Merge branch 'quentin' of gitlab.enst.fr:pact/2022-2023/pact71 into main
30
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Docker Node.js Launch",
|
||||||
|
"type": "docker",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "docker-run: debug",
|
||||||
|
"platform": "node",
|
||||||
|
"node": {
|
||||||
|
"package": "${workspaceFolder}/code/reviews_api/package.json",
|
||||||
|
"localRoot": "${workspaceFolder}/code/reviews_api"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Docker: Python - General",
|
||||||
|
"type": "docker",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "docker-run: debug",
|
||||||
|
"python": {
|
||||||
|
"pathMappings": [
|
||||||
|
{
|
||||||
|
"localRoot": "${workspaceFolder}",
|
||||||
|
"remoteRoot": "/app"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"projectType": "general"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
46
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "docker-build",
|
||||||
|
"label": "docker-build",
|
||||||
|
"platform": "node",
|
||||||
|
"dockerBuild": {
|
||||||
|
"dockerfile": "${workspaceFolder}/code/reviews_api/Dockerfile",
|
||||||
|
"context": "${workspaceFolder}/code/reviews_api",
|
||||||
|
"pull": true
|
||||||
|
},
|
||||||
|
"node": {
|
||||||
|
"package": "${workspaceFolder}/code/reviews_api/package.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docker-run",
|
||||||
|
"label": "docker-run: release",
|
||||||
|
"dependsOn": [
|
||||||
|
"docker-build"
|
||||||
|
],
|
||||||
|
"platform": "node",
|
||||||
|
"node": {
|
||||||
|
"package": "${workspaceFolder}/code/reviews_api/package.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docker-run",
|
||||||
|
"label": "docker-run: debug",
|
||||||
|
"dependsOn": [
|
||||||
|
"docker-build"
|
||||||
|
],
|
||||||
|
"dockerRun": {
|
||||||
|
"env": {
|
||||||
|
"DEBUG": "*",
|
||||||
|
"NODE_ENV": "development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node": {
|
||||||
|
"package": "${workspaceFolder}/code/reviews_api/package.json",
|
||||||
|
"enableDebugging": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
23
README.md
@@ -1,23 +0,0 @@
|
|||||||
Ceci est votre dépôt pour le projet PACT.
|
|
||||||
|
|
||||||
Vous **DEVEZ** modifier ce fichier (`README.md`) et créer tous les
|
|
||||||
répertoires et fichiers dont vous aurez besoin pour votre projet.
|
|
||||||
|
|
||||||
# Important, le rapport d'avancement
|
|
||||||
|
|
||||||
Le répertoire `rapport` contient un squelette pour votre rapporte d'avancement.
|
|
||||||
Ce répertoire **ne doit pas être renommé** ni le fichier `README.adoc` qu'il contient.
|
|
||||||
Le fichier `README.adoc` est le point d'entrée du rapport.
|
|
||||||
|
|
||||||
Il est rédigé en utilisant le langage [**AsciiDoc**](http://asciidoc.org/).
|
|
||||||
La syntaxe est supportée par GitLab qui le formatera correctement dans l'interface Web.
|
|
||||||
|
|
||||||
Le document final sera généré en utilisant l'outil [Asciidoctor](http://asciidoctor.org/) qui supporte les mêmes extensions que GitLab (pour les équations par exemple).
|
|
||||||
Un résumé de la syntaxe supportée est accessible [ici](http://asciidoctor.org/docs/asciidoc-syntax-quick-reference/).
|
|
||||||
|
|
||||||
Vous pouvez éditer les différents fichiers en utilisant *votre éditeur de texte favori*.
|
|
||||||
Si vous n'en avez pas, vous pouvez, par exemple utiliser:
|
|
||||||
|
|
||||||
- [**Visual Studio Code**](https://code.visualstudio.com/) avec l'extension [AsciiDoc](https://marketplace.visualstudio.com/items?itemName=asciidoctor.asciidoctor-vscode) qui ajoute coloration syntaxique et rendu en temps réel.
|
|
||||||
- Ou bien sûr votre éditeur de text préféré **Sublim Text**, **Vim**, **Emacs**,…
|
|
||||||
- Une extension pour navigateur Web est aussi disponible pour visualiser le résultat ([**ici**](https://github.com/asciidoctor/asciidoctor-browser-extension)).
|
|
||||||
27
code/backend_reconnaissance/.dockerignore
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
**/__pycache__
|
||||||
|
**/.venv
|
||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
20
code/backend_reconnaissance/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM python:3.8-slim
|
||||||
|
|
||||||
|
#Ne pas créer les fichiers .pyc
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
#Afficher les logs directement dans le terminal
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
#Installation des dépendances de opencv
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install ffmpeg libsm6 libxext6 -y
|
||||||
|
|
||||||
|
# Installation des dépendances python
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN python -m pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Création du répertoire de travail
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
CMD ["python", "main.py"]
|
||||||
BIN
code/backend_reconnaissance/__pycache__/hands.cpython-310.pyc
Normal file
@@ -1,29 +1,29 @@
|
|||||||
import requests
|
import requests
|
||||||
#Exemple ajout d'un commentaire depuis la borne (site ou geste)
|
#Exemple ajout d'un commentaire depuis la borne (site ou geste)
|
||||||
avis = {
|
avis = {
|
||||||
"note": 8,
|
"note": 8,
|
||||||
"source": "borne",
|
"source": "borne",
|
||||||
#Optionel
|
#Optionel
|
||||||
"auteur_age": 20,
|
"auteur_age": 20,
|
||||||
"notes_autre": '{"proprete":8,"calme":10}',
|
"notes_autre": '{"proprete":8,"calme":10}',
|
||||||
"auteur_sexe": 'f',
|
"auteur_sexe": 'f',
|
||||||
"commentaire": "Commentaire"
|
"commentaire": "Commentaire"
|
||||||
}
|
}
|
||||||
|
|
||||||
# res = requests.post("http://localhost:8080/add_review", data=avis)
|
res = requests.post("http://localhost:8080/add_review", data=avis)
|
||||||
# print(res.text)
|
# print(res.text)
|
||||||
|
|
||||||
#Exemple ajout d'un commentaire trouvé sur les réseaux sociaux
|
#Exemple ajout d'un commentaire trouvé sur les réseaux sociaux
|
||||||
avis = {
|
avis = {
|
||||||
"auteur_nom": "michel",
|
"auteur_nom": "michel",
|
||||||
"source": "instagram",
|
"source": "instagram",
|
||||||
"note": 8,
|
"note": 8,
|
||||||
"date": "2022-12-24",
|
"date": "2022-12-24",
|
||||||
#Optionel
|
#Optionel
|
||||||
"commentaire": "J'ai beaucoup aimé !",
|
"commentaire": "J'ai beaucoup aimé !",
|
||||||
"lien": "https://instagram.com/si_insta_avait_des_liens_vers_des_commentaires_faudrait_le_mettre_ici",
|
"lien": "https://instagram.com/si_insta_avait_des_liens_vers_des_commentaires_faudrait_le_mettre_ici",
|
||||||
"auteur_lien": "https://instagram.com/michel",
|
"auteur_lien": "https://instagram.com/michel",
|
||||||
}
|
}
|
||||||
|
|
||||||
res = requests.post("http://localhost:8080/add_social_review", data=avis)
|
# res = requests.post("http://localhost:8080/add_social_review", data=avis)
|
||||||
print(res.text)
|
print(res.text)
|
||||||
44
code/backend_reconnaissance/hands.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import cv2
|
||||||
|
import mediapipe as mp
|
||||||
|
mp_drawing = mp.solutions.drawing_utils
|
||||||
|
mp_drawing_styles = mp.solutions.drawing_styles
|
||||||
|
mp_hands = mp.solutions.hands
|
||||||
|
|
||||||
|
# For webcam input:
|
||||||
|
cap = cv2.VideoCapture(0)
|
||||||
|
hands = mp_hands.Hands(
|
||||||
|
model_complexity=0,
|
||||||
|
min_detection_confidence=0.5,
|
||||||
|
min_tracking_confidence=0.5)
|
||||||
|
|
||||||
|
def frame():
|
||||||
|
if cap.isOpened():
|
||||||
|
success, image = cap.read()
|
||||||
|
if not success:
|
||||||
|
print("Ignoring empty camera frame.")
|
||||||
|
# If loading a video, use 'break' instead of 'continue'.
|
||||||
|
return
|
||||||
|
|
||||||
|
# To improve performance, optionally mark the image as not writeable to
|
||||||
|
# pass by reference.
|
||||||
|
image.flags.writeable = False
|
||||||
|
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||||
|
results = hands.process(image)
|
||||||
|
|
||||||
|
# Draw the hand annotations on the image.
|
||||||
|
image.flags.writeable = True
|
||||||
|
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
||||||
|
if results.multi_hand_landmarks:
|
||||||
|
for hand_landmarks in results.multi_hand_landmarks:
|
||||||
|
mp_drawing.draw_landmarks(
|
||||||
|
image,
|
||||||
|
hand_landmarks,
|
||||||
|
mp_hands.HAND_CONNECTIONS,
|
||||||
|
mp_drawing_styles.get_default_hand_landmarks_style(),
|
||||||
|
mp_drawing_styles.get_default_hand_connections_style())
|
||||||
|
# Flip the image horizontally for a selfie-view display.
|
||||||
|
# cv2.imshow('MediaPipe Hands', cv2.flip(image, 1))
|
||||||
|
if cv2.waitKey(5) & 0xFF == 27:
|
||||||
|
return
|
||||||
|
# cap.release()
|
||||||
|
|
||||||
39
code/backend_reconnaissance/main.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import websockets
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
import hands
|
||||||
|
import time
|
||||||
|
|
||||||
|
values = []
|
||||||
|
class WebsocketServer:
|
||||||
|
def __init__(self,getEffects,port=os.getenv("PORT"),host=os.getenv("HOST")) -> None:
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.getEffects = getEffects
|
||||||
|
|
||||||
|
async def run(self):
|
||||||
|
async with websockets.serve(self.handler, self.host, self.port):
|
||||||
|
await asyncio.Future()
|
||||||
|
|
||||||
|
|
||||||
|
async def handler(self,websocket):
|
||||||
|
while True:
|
||||||
|
start = time.time()
|
||||||
|
messages = self.getEffects()
|
||||||
|
hands.frame()
|
||||||
|
await websocket.send(json.dumps(messages))
|
||||||
|
# await asyncio.sleep(1/30)
|
||||||
|
delay = time.time() - start
|
||||||
|
values.append(1/delay)
|
||||||
|
avg = sum(values) / len(values)
|
||||||
|
dev = [(v - avg) ** 2 for v in values]
|
||||||
|
print(avg, math.sqrt(sum(dev)/len(dev)))
|
||||||
|
#Remplacer ça par la fonction qui récupère les effets (dans le module de reconnaissance de gestes)
|
||||||
|
def getEffects():
|
||||||
|
return {"type": "effects", "effects": [{"type": "thumbs_up", "x":random.randint(0,100), "y": random.randint(0,100), "width": 50, "height": 50}]}
|
||||||
|
|
||||||
|
server = WebsocketServer(getEffects)
|
||||||
|
asyncio.run(server.run())
|
||||||
4
code/backend_reconnaissance/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
websockets
|
||||||
|
requests
|
||||||
|
opencv-python
|
||||||
|
mediapipe
|
||||||
@@ -1,444 +1,425 @@
|
|||||||
-- phpMyAdmin SQL Dump
|
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||||
-- version 4.9.5deb2
|
SET AUTOCOMMIT = 0;
|
||||||
-- https://www.phpmyadmin.net/
|
START TRANSACTION;
|
||||||
--
|
|
||||||
-- Host: localhost:3306
|
|
||||||
-- Generation Time: Dec 26, 2022 at 10:31 AM
|
--
|
||||||
-- Server version: 8.0.31-0ubuntu0.20.04.1
|
-- Database: `telereview`
|
||||||
-- PHP Version: 7.4.3
|
--
|
||||||
|
CREATE DATABASE IF NOT EXISTS `telereview`;
|
||||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
USE `telereview`;
|
||||||
SET AUTOCOMMIT = 0;
|
|
||||||
START TRANSACTION;
|
-- --------------------------------------------------------
|
||||||
SET time_zone = "+00:00";
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `borne_auteurs`
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
--
|
||||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
CREATE TABLE `borne_auteurs` (
|
||||||
/*!40101 SET NAMES utf8mb4 */;
|
`id` int NOT NULL,
|
||||||
|
`sexe` tinytext ,
|
||||||
--
|
`age` tinyint DEFAULT NULL
|
||||||
-- Database: `telereview`
|
) ;
|
||||||
--
|
|
||||||
CREATE DATABASE IF NOT EXISTS `telereview` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
-- --------------------------------------------------------
|
||||||
USE `telereview`;
|
|
||||||
|
--
|
||||||
-- --------------------------------------------------------
|
-- Table structure for table `borne_avis`
|
||||||
|
--
|
||||||
--
|
|
||||||
-- Table structure for table `borne_auteurs`
|
CREATE TABLE `borne_avis` (
|
||||||
--
|
`id` int NOT NULL,
|
||||||
|
`id_auteur` int NOT NULL,
|
||||||
CREATE TABLE `borne_auteurs` (
|
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`id` int NOT NULL,
|
`note_principale` tinyint NOT NULL,
|
||||||
`sexe` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
|
`commentaire` text NOT NULL,
|
||||||
`age` tinyint DEFAULT NULL
|
`source_id` int NOT NULL
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
) ;
|
||||||
|
|
||||||
-- --------------------------------------------------------
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Table structure for table `borne_avis`
|
-- Table structure for table `borne_criteres`
|
||||||
--
|
--
|
||||||
|
|
||||||
CREATE TABLE `borne_avis` (
|
CREATE TABLE `borne_criteres` (
|
||||||
`id` int NOT NULL,
|
`id` int NOT NULL,
|
||||||
`id_auteur` int NOT NULL,
|
`nom` text NOT NULL
|
||||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
) ;
|
||||||
`note_principale` tinyint NOT NULL,
|
|
||||||
`commentaire` text NOT NULL,
|
--
|
||||||
`source_id` int NOT NULL
|
-- Dumping data for table `borne_criteres`
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
--
|
||||||
|
|
||||||
-- --------------------------------------------------------
|
INSERT INTO `borne_criteres` (`id`, `nom`) VALUES
|
||||||
|
(1, 'proprete'),
|
||||||
--
|
(2, 'calme'),
|
||||||
-- Table structure for table `borne_criteres`
|
(3, 'attente');
|
||||||
--
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
CREATE TABLE `borne_criteres` (
|
|
||||||
`id` int NOT NULL,
|
--
|
||||||
`nom` text NOT NULL
|
-- Table structure for table `borne_notes_autre`
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
--
|
||||||
|
|
||||||
--
|
CREATE TABLE `borne_notes_autre` (
|
||||||
-- Dumping data for table `borne_criteres`
|
`id` int NOT NULL,
|
||||||
--
|
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`critere_id` int NOT NULL,
|
||||||
INSERT INTO `borne_criteres` (`id`, `nom`) VALUES
|
`avis_id` int NOT NULL,
|
||||||
(1, 'proprete'),
|
`note` int NOT NULL COMMENT 'Note sur 10'
|
||||||
(2, 'calme'),
|
) ;
|
||||||
(3, 'attente');
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
-- --------------------------------------------------------
|
|
||||||
|
--
|
||||||
--
|
-- Table structure for table `reseaux_sociaux_auteurs`
|
||||||
-- Table structure for table `borne_notes_autre`
|
--
|
||||||
--
|
|
||||||
|
CREATE TABLE `reseaux_sociaux_auteurs` (
|
||||||
CREATE TABLE `borne_notes_autre` (
|
`id` int NOT NULL,
|
||||||
`id` int NOT NULL,
|
`nom_utilisateur` text NOT NULL,
|
||||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`source_id` int NOT NULL,
|
||||||
`critere_id` int NOT NULL,
|
`lien` text NOT NULL
|
||||||
`avis_id` int NOT NULL,
|
) ;
|
||||||
`note` int NOT NULL COMMENT 'Note sur 10'
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
-- --------------------------------------------------------
|
--
|
||||||
|
-- Table structure for table `reseaux_sociaux_avis`
|
||||||
--
|
--
|
||||||
-- Table structure for table `reseaux_sociaux_auteurs`
|
|
||||||
--
|
CREATE TABLE `reseaux_sociaux_avis` (
|
||||||
|
`id` int NOT NULL,
|
||||||
CREATE TABLE `reseaux_sociaux_auteurs` (
|
`date` date NOT NULL,
|
||||||
`id` int NOT NULL,
|
`source_id` int NOT NULL,
|
||||||
`nom_utilisateur` text NOT NULL,
|
`note` tinyint DEFAULT NULL,
|
||||||
`source_id` int NOT NULL,
|
`commentaire` text ,
|
||||||
`lien` text NOT NULL
|
`auteur_id` int NOT NULL,
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
`lien_source` text
|
||||||
|
) ;
|
||||||
-- --------------------------------------------------------
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
--
|
|
||||||
-- Table structure for table `reseaux_sociaux_avis`
|
--
|
||||||
--
|
-- Table structure for table `sources`
|
||||||
|
--
|
||||||
CREATE TABLE `reseaux_sociaux_avis` (
|
|
||||||
`id` int NOT NULL,
|
CREATE TABLE `sources` (
|
||||||
`date` date NOT NULL,
|
`id` int NOT NULL,
|
||||||
`source_id` int NOT NULL,
|
`nom` text NOT NULL
|
||||||
`note` tinyint DEFAULT NULL,
|
) ;
|
||||||
`commentaire` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
|
|
||||||
`auteur_id` int NOT NULL,
|
--
|
||||||
`lien_source` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci
|
-- Dumping data for table `sources`
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
--
|
||||||
|
|
||||||
-- --------------------------------------------------------
|
INSERT INTO `sources` (`id`, `nom`) VALUES
|
||||||
|
(1, 'website'),
|
||||||
--
|
(2, 'borne'),
|
||||||
-- Table structure for table `sources`
|
(3, 'instagram');
|
||||||
--
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
CREATE TABLE `sources` (
|
|
||||||
`id` int NOT NULL,
|
--
|
||||||
`nom` text NOT NULL
|
-- Table structure for table `stats_autres_annee`
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
--
|
||||||
|
|
||||||
--
|
CREATE TABLE `stats_autres_annee` (
|
||||||
-- Dumping data for table `sources`
|
`id` int NOT NULL,
|
||||||
--
|
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`critere_id` int NOT NULL,
|
||||||
INSERT INTO `sources` (`id`, `nom`) VALUES
|
`note` float NOT NULL
|
||||||
(1, 'website'),
|
) ;
|
||||||
(2, 'borne'),
|
|
||||||
(3, 'instagram');
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
-- --------------------------------------------------------
|
--
|
||||||
|
-- Table structure for table `stats_autres_jour`
|
||||||
--
|
--
|
||||||
-- Table structure for table `stats_autres_annee`
|
|
||||||
--
|
CREATE TABLE `stats_autres_jour` (
|
||||||
|
`id` int NOT NULL,
|
||||||
CREATE TABLE `stats_autres_annee` (
|
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`id` int NOT NULL,
|
`critere_id` int NOT NULL,
|
||||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`note` float NOT NULL
|
||||||
`critere_id` int NOT NULL,
|
) ;
|
||||||
`note` float NOT NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
-- --------------------------------------------------------
|
--
|
||||||
|
-- Table structure for table `stats_autres_mois`
|
||||||
--
|
--
|
||||||
-- Table structure for table `stats_autres_jour`
|
|
||||||
--
|
CREATE TABLE `stats_autres_mois` (
|
||||||
|
`id` int NOT NULL,
|
||||||
CREATE TABLE `stats_autres_jour` (
|
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`id` int NOT NULL,
|
`critere_id` int NOT NULL,
|
||||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`note` float NOT NULL
|
||||||
`critere_id` int NOT NULL,
|
) ;
|
||||||
`note` float NOT NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
-- --------------------------------------------------------
|
--
|
||||||
|
-- Table structure for table `stats_autres_semaine`
|
||||||
--
|
--
|
||||||
-- Table structure for table `stats_autres_mois`
|
|
||||||
--
|
CREATE TABLE `stats_autres_semaine` (
|
||||||
|
`id` int NOT NULL,
|
||||||
CREATE TABLE `stats_autres_mois` (
|
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`id` int NOT NULL,
|
`critere_id` int NOT NULL,
|
||||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`note` float NOT NULL
|
||||||
`critere_id` int NOT NULL,
|
) ;
|
||||||
`note` float NOT NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
-- --------------------------------------------------------
|
--
|
||||||
|
-- Table structure for table `stats_general_annee`
|
||||||
--
|
--
|
||||||
-- Table structure for table `stats_autres_semaine`
|
|
||||||
--
|
CREATE TABLE `stats_general_annee` (
|
||||||
|
`id` int NOT NULL,
|
||||||
CREATE TABLE `stats_autres_semaine` (
|
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`id` int NOT NULL,
|
`moyenne_globale` float NOT NULL,
|
||||||
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`moyenne_site` float NOT NULL,
|
||||||
`critere_id` int NOT NULL,
|
`moyenne_borne` float NOT NULL,
|
||||||
`note` float NOT NULL
|
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
||||||
|
) ;
|
||||||
-- --------------------------------------------------------
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
--
|
|
||||||
-- Table structure for table `stats_general_annee`
|
--
|
||||||
--
|
-- Table structure for table `stats_general_jour`
|
||||||
|
--
|
||||||
CREATE TABLE `stats_general_annee` (
|
|
||||||
`id` int NOT NULL,
|
CREATE TABLE `stats_general_jour` (
|
||||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`id` int NOT NULL,
|
||||||
`moyenne_globale` float NOT NULL,
|
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`moyenne_site` float NOT NULL,
|
`moyenne_globale` float DEFAULT NULL,
|
||||||
`moyenne_borne` float NOT NULL,
|
`moyenne_site` float DEFAULT NULL,
|
||||||
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
`moyenne_borne` float DEFAULT NULL,
|
||||||
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
`dist_age` text COMMENT 'Distribution de l''age des auteurs',
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
`dist_sexe` text COMMENT 'Distribution du sexe des auteurs'
|
||||||
|
) ;
|
||||||
-- --------------------------------------------------------
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
--
|
|
||||||
-- Table structure for table `stats_general_jour`
|
--
|
||||||
--
|
-- Table structure for table `stats_general_mois`
|
||||||
|
--
|
||||||
CREATE TABLE `stats_general_jour` (
|
|
||||||
`id` int NOT NULL,
|
CREATE TABLE `stats_general_mois` (
|
||||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`id` int NOT NULL,
|
||||||
`moyenne_globale` float DEFAULT NULL,
|
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`moyenne_site` float DEFAULT NULL,
|
`moyenne_globale` float NOT NULL,
|
||||||
`moyenne_borne` float DEFAULT NULL,
|
`moyenne_site` float NOT NULL,
|
||||||
`dist_age` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'Distribution de l''age des auteurs',
|
`moyenne_borne` float NOT NULL,
|
||||||
`dist_sexe` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'Distribution du sexe des auteurs'
|
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
||||||
|
) ;
|
||||||
-- --------------------------------------------------------
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
--
|
|
||||||
-- Table structure for table `stats_general_mois`
|
--
|
||||||
--
|
-- Table structure for table `stats_general_semaine`
|
||||||
|
--
|
||||||
CREATE TABLE `stats_general_mois` (
|
|
||||||
`id` int NOT NULL,
|
CREATE TABLE `stats_general_semaine` (
|
||||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`id` int NOT NULL,
|
||||||
`moyenne_globale` float NOT NULL,
|
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
`moyenne_site` float NOT NULL,
|
`moyenne_globale` float NOT NULL,
|
||||||
`moyenne_borne` float NOT NULL,
|
`moyenne_site` float NOT NULL,
|
||||||
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
`moyenne_borne` float NOT NULL,
|
||||||
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
||||||
|
) ;
|
||||||
-- --------------------------------------------------------
|
|
||||||
|
--
|
||||||
--
|
-- Indexes for dumped tables
|
||||||
-- Table structure for table `stats_general_semaine`
|
--
|
||||||
--
|
|
||||||
|
--
|
||||||
CREATE TABLE `stats_general_semaine` (
|
-- Indexes for table `borne_auteurs`
|
||||||
`id` int NOT NULL,
|
--
|
||||||
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
ALTER TABLE `borne_auteurs`
|
||||||
`moyenne_globale` float NOT NULL,
|
ADD PRIMARY KEY (`id`);
|
||||||
`moyenne_site` float NOT NULL,
|
|
||||||
`moyenne_borne` float NOT NULL,
|
--
|
||||||
`dist_age` text NOT NULL COMMENT 'Distribution de l''age des auteurs',
|
-- Indexes for table `borne_avis`
|
||||||
`dist_sexe` text NOT NULL COMMENT 'Distribution du sexe des auteurs'
|
--
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
ALTER TABLE `borne_avis`
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
-- Indexes for dumped tables
|
--
|
||||||
--
|
-- Indexes for table `borne_criteres`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `borne_criteres`
|
||||||
-- Indexes for table `borne_auteurs`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `borne_auteurs`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `borne_notes_autre`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `borne_notes_autre`
|
||||||
-- Indexes for table `borne_avis`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `borne_avis`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `reseaux_sociaux_auteurs`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `reseaux_sociaux_auteurs`
|
||||||
-- Indexes for table `borne_criteres`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `borne_criteres`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `reseaux_sociaux_avis`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `reseaux_sociaux_avis`
|
||||||
-- Indexes for table `borne_notes_autre`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `borne_notes_autre`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `sources`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `sources`
|
||||||
-- Indexes for table `reseaux_sociaux_auteurs`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `reseaux_sociaux_auteurs`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `stats_autres_annee`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_autres_annee`
|
||||||
-- Indexes for table `reseaux_sociaux_avis`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `reseaux_sociaux_avis`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `stats_autres_jour`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_autres_jour`
|
||||||
-- Indexes for table `sources`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `sources`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `stats_autres_mois`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_autres_mois`
|
||||||
-- Indexes for table `stats_autres_annee`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `stats_autres_annee`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `stats_autres_semaine`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_autres_semaine`
|
||||||
-- Indexes for table `stats_autres_jour`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `stats_autres_jour`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `stats_general_annee`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_general_annee`
|
||||||
-- Indexes for table `stats_autres_mois`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `stats_autres_mois`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `stats_general_jour`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_general_jour`
|
||||||
-- Indexes for table `stats_autres_semaine`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `stats_autres_semaine`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `stats_general_mois`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_general_mois`
|
||||||
-- Indexes for table `stats_general_annee`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `stats_general_annee`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- Indexes for table `stats_general_semaine`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_general_semaine`
|
||||||
-- Indexes for table `stats_general_jour`
|
ADD PRIMARY KEY (`id`);
|
||||||
--
|
|
||||||
ALTER TABLE `stats_general_jour`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
-- AUTO_INCREMENT for dumped tables
|
||||||
|
--
|
||||||
--
|
|
||||||
-- Indexes for table `stats_general_mois`
|
--
|
||||||
--
|
-- AUTO_INCREMENT for table `borne_auteurs`
|
||||||
ALTER TABLE `stats_general_mois`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
ALTER TABLE `borne_auteurs`
|
||||||
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
-- Indexes for table `stats_general_semaine`
|
--
|
||||||
--
|
-- AUTO_INCREMENT for table `borne_avis`
|
||||||
ALTER TABLE `stats_general_semaine`
|
--
|
||||||
ADD PRIMARY KEY (`id`);
|
ALTER TABLE `borne_avis`
|
||||||
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
-- AUTO_INCREMENT for dumped tables
|
--
|
||||||
--
|
-- AUTO_INCREMENT for table `borne_criteres`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `borne_criteres`
|
||||||
-- AUTO_INCREMENT for table `borne_auteurs`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||||
--
|
|
||||||
ALTER TABLE `borne_auteurs`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
-- AUTO_INCREMENT for table `borne_notes_autre`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `borne_notes_autre`
|
||||||
-- AUTO_INCREMENT for table `borne_avis`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
ALTER TABLE `borne_avis`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
-- AUTO_INCREMENT for table `reseaux_sociaux_auteurs`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `reseaux_sociaux_auteurs`
|
||||||
-- AUTO_INCREMENT for table `borne_criteres`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
ALTER TABLE `borne_criteres`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
-- AUTO_INCREMENT for table `reseaux_sociaux_avis`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `reseaux_sociaux_avis`
|
||||||
-- AUTO_INCREMENT for table `borne_notes_autre`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
ALTER TABLE `borne_notes_autre`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
-- AUTO_INCREMENT for table `sources`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `sources`
|
||||||
-- AUTO_INCREMENT for table `reseaux_sociaux_auteurs`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||||
--
|
|
||||||
ALTER TABLE `reseaux_sociaux_auteurs`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
-- AUTO_INCREMENT for table `stats_autres_annee`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_autres_annee`
|
||||||
-- AUTO_INCREMENT for table `reseaux_sociaux_avis`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
ALTER TABLE `reseaux_sociaux_avis`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
-- AUTO_INCREMENT for table `stats_autres_jour`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_autres_jour`
|
||||||
-- AUTO_INCREMENT for table `sources`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
ALTER TABLE `sources`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
-- AUTO_INCREMENT for table `stats_autres_mois`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_autres_mois`
|
||||||
-- AUTO_INCREMENT for table `stats_autres_annee`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
ALTER TABLE `stats_autres_annee`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
-- AUTO_INCREMENT for table `stats_autres_semaine`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_autres_semaine`
|
||||||
-- AUTO_INCREMENT for table `stats_autres_jour`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
ALTER TABLE `stats_autres_jour`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
-- AUTO_INCREMENT for table `stats_general_annee`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_general_annee`
|
||||||
-- AUTO_INCREMENT for table `stats_autres_mois`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
ALTER TABLE `stats_autres_mois`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
-- AUTO_INCREMENT for table `stats_general_jour`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_general_jour`
|
||||||
-- AUTO_INCREMENT for table `stats_autres_semaine`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
ALTER TABLE `stats_autres_semaine`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
-- AUTO_INCREMENT for table `stats_general_mois`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_general_mois`
|
||||||
-- AUTO_INCREMENT for table `stats_general_annee`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
|
||||||
ALTER TABLE `stats_general_annee`
|
--
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
-- AUTO_INCREMENT for table `stats_general_semaine`
|
||||||
|
--
|
||||||
--
|
ALTER TABLE `stats_general_semaine`
|
||||||
-- AUTO_INCREMENT for table `stats_general_jour`
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
--
|
COMMIT;
|
||||||
ALTER TABLE `stats_general_jour`
|
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- AUTO_INCREMENT for table `stats_general_mois`
|
|
||||||
--
|
|
||||||
ALTER TABLE `stats_general_mois`
|
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
|
||||||
|
|
||||||
--
|
|
||||||
-- AUTO_INCREMENT for table `stats_general_semaine`
|
|
||||||
--
|
|
||||||
ALTER TABLE `stats_general_semaine`
|
|
||||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
|
||||||
COMMIT;
|
|
||||||
|
|
||||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
|
||||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
|
||||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
|
||||||
98
code/docker-compose.yaml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
version: "3.9"
|
||||||
|
services:
|
||||||
|
|
||||||
|
#Base de donnée mysql de la borne sur laquelle est stockée tous les avis et les stats
|
||||||
|
db:
|
||||||
|
image: mysql:latest
|
||||||
|
container_name: db
|
||||||
|
expose:
|
||||||
|
- 3306
|
||||||
|
volumes:
|
||||||
|
- ./db:/docker-entrypoint-initdb.d
|
||||||
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "-uroot"] # Command to check health.
|
||||||
|
interval: 5s # Interval between health checks.
|
||||||
|
timeout: 5s # Timeout for each health checking.
|
||||||
|
retries: 20 # Hou many times retries.
|
||||||
|
start_period: 10s # Estimated time to boot.
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: telereview
|
||||||
|
MYSQL_DATABASE: telereview
|
||||||
|
|
||||||
|
#Interface d'aministration pour la bdd
|
||||||
|
phpmyadmin:
|
||||||
|
image: phpmyadmin:latest
|
||||||
|
restart: always
|
||||||
|
container_name: phpmyadmin
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
PMA_ARBITRARY: 1
|
||||||
|
PMA_HOST: db
|
||||||
|
PMA_USER: root
|
||||||
|
PMA_PASSWORD: telereview
|
||||||
|
ports:
|
||||||
|
- 8000:80
|
||||||
|
|
||||||
|
#API de gestion des avis, permet d'ajouter ou de récuperer des avis ou les stats sur les avis par des requêtes HTTP
|
||||||
|
reviews_api:
|
||||||
|
container_name: reviews_api
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- DB_USER=root
|
||||||
|
- DB_PASSWORD=telereview
|
||||||
|
- DB_HOST=db
|
||||||
|
- DB_NAME=telereview
|
||||||
|
- PORT=8080
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
build: ./reviews_api
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
#Serveur web de l'interface de la borne
|
||||||
|
interface_borne:
|
||||||
|
image: httpd:latest
|
||||||
|
volumes:
|
||||||
|
- ./interface_borne:/usr/local/apache2/htdocs/
|
||||||
|
container_name: interface_borne
|
||||||
|
ports:
|
||||||
|
- 8888:80
|
||||||
|
|
||||||
|
#Serveur web de l'interface admin
|
||||||
|
interface_admin:
|
||||||
|
image: httpd:latest
|
||||||
|
volumes:
|
||||||
|
- ./interface_admin:/usr/local/apache2/htdocs/
|
||||||
|
container_name: interface_admin
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
|
||||||
|
#Backend de la borne : scripts pythons de reconnaissances video et audio
|
||||||
|
#Envoient les infos a l'interface de la borne par websocket pour mettre a jour l'interface rapidement
|
||||||
|
#Met a jour les avis en faisant des requêtes a l'API
|
||||||
|
backend_reconnaissance:
|
||||||
|
build: ./backend_reconnaissance
|
||||||
|
container_name: backend_reconnaissance
|
||||||
|
restart: always
|
||||||
|
devices:
|
||||||
|
- /dev/video3:/dev/video0
|
||||||
|
environment:
|
||||||
|
- PORT=5000
|
||||||
|
- HOST=backend_reconnaissance
|
||||||
|
ports:
|
||||||
|
#Ce container est le serveur websocker dont le client est l'interface de la borne qui tourne dans le navigateur
|
||||||
|
- 5000:5000
|
||||||
|
|
||||||
|
video_loopback:
|
||||||
|
build: ./video_loopback
|
||||||
|
container_name: video_loopback
|
||||||
|
restart: always
|
||||||
|
devices:
|
||||||
|
- /dev/video0:/dev/video0
|
||||||
|
- /dev/video2:/dev/video1
|
||||||
|
- /dev/video3:/dev/video2
|
||||||
2
code/interface_admin/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.next
|
||||||
|
package-lock.json
|
||||||
34
code/interface_admin/README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
|
||||||
|
|
||||||
|
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||||
58
code/interface_admin/components/Avis.jsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Card, Col, Row, Table } from 'react-bootstrap';
|
||||||
|
import { BsPersonFill } from 'react-icons/bs';
|
||||||
|
import styles from '../styles/Avis.module.css'
|
||||||
|
|
||||||
|
export default function Avis({review}) {
|
||||||
|
const {date, note_principale,notes_autres, commentaire, sexe_auteur, nom_source, age_auteur} = review;
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<Card.Title>Avis</Card.Title>
|
||||||
|
<Card.Body>
|
||||||
|
<Row>
|
||||||
|
<h2>Auteur</h2>
|
||||||
|
<Col xs={1}>
|
||||||
|
<BsPersonFill className={styles.personIcon} />
|
||||||
|
</Col>
|
||||||
|
<Col className='d-flex flex-column'>
|
||||||
|
<p>Age : {age_auteur}</p>
|
||||||
|
<p>Sexe : {sexe_auteur}</p>
|
||||||
|
<p>Date de publication : {date}</p>
|
||||||
|
<p>Source : {nom_source}</p>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<h2>Notes</h2>
|
||||||
|
<Table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Critère</th>
|
||||||
|
<th>Note</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Général</td>
|
||||||
|
<td>{note_principale} / 10</td>
|
||||||
|
</tr>
|
||||||
|
{notes_autres && notes_autres.map(({ critere, note }) => {
|
||||||
|
return <tr key={critere}>
|
||||||
|
<td>{critere}</td>
|
||||||
|
<td>{note}/10</td>
|
||||||
|
</tr>
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Card>
|
||||||
|
<Card.Header>Commentaire</Card.Header>
|
||||||
|
<Card.Body>
|
||||||
|
{commentaire}
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Row>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
34
code/interface_admin/components/AvisList.jsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import React from 'react'
|
||||||
|
import { Table } from 'react-bootstrap'
|
||||||
|
import styles from '../styles/AvisList.module.css'
|
||||||
|
|
||||||
|
export default function AvisList({ avis }) {
|
||||||
|
const router = useRouter();
|
||||||
|
function handleClick(id) {
|
||||||
|
router.push(`/avis/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Note globale</th>
|
||||||
|
<th>Commentaire</th>
|
||||||
|
<th>Source</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{avis.map(({ id, note_principale, commentaire, date, nom_source }) => {
|
||||||
|
return <tr onClick={() => handleClick(id)} key={id} className={styles.row}>
|
||||||
|
<td>{date}</td>
|
||||||
|
<td>{note_principale} / 10</td>
|
||||||
|
<td>{commentaire}</td>
|
||||||
|
<td>{nom_source}</td>
|
||||||
|
</tr>
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
)
|
||||||
|
}
|
||||||
47
code/interface_admin/components/ComparativeBarChart.jsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Bar } from 'react-chartjs-2'
|
||||||
|
import Chart from 'chart.js/auto';
|
||||||
|
|
||||||
|
export default function ComparativeBarChart({ xlabels, data0, label0, data1, label1}) {
|
||||||
|
return (
|
||||||
|
<Bar
|
||||||
|
options={{
|
||||||
|
// plugins: {
|
||||||
|
// title: {
|
||||||
|
// display: true,
|
||||||
|
// text: title
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
responsive: true,
|
||||||
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
stacked: true,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
stacked: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
data={{
|
||||||
|
labels: xlabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: label0,
|
||||||
|
data: data0,
|
||||||
|
backgroundColor: "#FF3B30",
|
||||||
|
stack: "stack0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: label1,
|
||||||
|
data: data1,
|
||||||
|
backgroundColor: "#0000FF",
|
||||||
|
stack: "stack1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
23
code/interface_admin/components/Menu.jsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import Container from 'react-bootstrap/Container';
|
||||||
|
import Nav from 'react-bootstrap/Nav';
|
||||||
|
import Navbar from 'react-bootstrap/Navbar';
|
||||||
|
|
||||||
|
export default function Menu() {
|
||||||
|
return (
|
||||||
|
<Navbar bg="light" expand="lg">
|
||||||
|
<Container>
|
||||||
|
<Navbar.Brand href="#home">Téléreview</Navbar.Brand>
|
||||||
|
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||||
|
<Navbar.Collapse id="basic-navbar-nav">
|
||||||
|
<Nav className="me-auto">
|
||||||
|
<Link href="/" passHref legacyBehavior><Nav.Link>Accueil</Nav.Link></Link>
|
||||||
|
<Link href="/stats" passHref legacyBehavior><Nav.Link>Statistiques</Nav.Link></Link>
|
||||||
|
<Link href="/avis" passHref legacyBehavior><Nav.Link>Avis</Nav.Link></Link>
|
||||||
|
</Nav>
|
||||||
|
</Navbar.Collapse>
|
||||||
|
</Container>
|
||||||
|
</Navbar>
|
||||||
|
)
|
||||||
|
}
|
||||||
3
code/interface_admin/config/reviewsApi.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const api = {
|
||||||
|
HOST: 'localhost:8080'
|
||||||
|
}
|
||||||
32
code/interface_admin/hooks/review.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { api } from "../config/reviewsApi";
|
||||||
|
|
||||||
|
|
||||||
|
function useReview(reviewId) {
|
||||||
|
const [review, setReview] = useState({});
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
async function fetchData(id) {
|
||||||
|
const response = await fetch('http://' + api.HOST + `/borne/get_review?id=${id}`)
|
||||||
|
if (response.ok) {
|
||||||
|
const jsonData = await response.json();
|
||||||
|
setReview(jsonData);
|
||||||
|
setLoading(false);
|
||||||
|
setError(false);
|
||||||
|
} else {
|
||||||
|
setError(true);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (reviewId) {
|
||||||
|
fetchData(reviewId);
|
||||||
|
}
|
||||||
|
}, [reviewId])
|
||||||
|
|
||||||
|
return { review, loading, error }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useReview;
|
||||||
30
code/interface_admin/hooks/reviews.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { api } from "../config/reviewsApi";
|
||||||
|
|
||||||
|
export default function useReviews() {
|
||||||
|
const [reviews, setReviews] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
async function fetchLastReviews(limit=100) {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await fetch('http://' + api.HOST + '/borne/get_last_reviews', {
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
if(response.ok) {
|
||||||
|
let json = await response.json()
|
||||||
|
setReviews(json);
|
||||||
|
setError(false);
|
||||||
|
setLoading(false);
|
||||||
|
}else {
|
||||||
|
setLoading(false);
|
||||||
|
setError(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchLastReviews();
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return {reviews, error, loading, fetchLastReviews};
|
||||||
|
}
|
||||||
26
code/interface_admin/hooks/stats.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { api } from "../config/reviewsApi";
|
||||||
|
|
||||||
|
export default function useStats(limit, interval) {
|
||||||
|
const [stats, setStats] = useState({});
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
async function fetchData(limit, interval) {
|
||||||
|
const response = await fetch("http://" + api.HOST + `/get_stats?interval=${interval}&limit=${limit}`)
|
||||||
|
if(response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setStats(data);
|
||||||
|
setError(false);
|
||||||
|
}else {
|
||||||
|
setError(true)
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData(limit, interval);
|
||||||
|
})
|
||||||
|
|
||||||
|
return {stats, loading, error};
|
||||||
|
}
|
||||||
6
code/interface_admin/next.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
||||||
24
code/interface_admin/package.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "interface-admin",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@next/font": "13.1.6",
|
||||||
|
"bootstrap": "^5.2.3",
|
||||||
|
"chart.js": "^4.2.0",
|
||||||
|
"eslint": "8.33.0",
|
||||||
|
"eslint-config-next": "13.1.6",
|
||||||
|
"next": "13.1.6",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-bootstrap": "^2.7.0",
|
||||||
|
"react-chartjs-2": "^5.2.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"react-icons": "^4.7.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
code/interface_admin/pages/_app.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import Menu from '../components/Menu'
|
||||||
|
import '../styles/globals.css'
|
||||||
|
import 'bootstrap/dist/css/bootstrap.css';
|
||||||
|
import { Container } from 'react-bootstrap';
|
||||||
|
|
||||||
|
export default function App({ Component, pageProps }) {
|
||||||
|
return <>
|
||||||
|
<Menu />
|
||||||
|
<Container fluid="md">
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
}
|
||||||
16
code/interface_admin/pages/_document.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Html, Head, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
|
export default function Document() {
|
||||||
|
return (
|
||||||
|
<Html lang="en">
|
||||||
|
<Head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
)
|
||||||
|
}
|
||||||
12
code/interface_admin/pages/avis/[id].js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import React from 'react'
|
||||||
|
import Avis from '../../components/Avis';
|
||||||
|
import useReview from '../../hooks/review';
|
||||||
|
export default function AvisPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const {id} = router.query;
|
||||||
|
const {review, loading, error} = useReview(id);
|
||||||
|
return (
|
||||||
|
!loading && !error && <Avis review={review}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
75
code/interface_admin/pages/avis/index.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { Card, Container, Form, Row, Table } from 'react-bootstrap'
|
||||||
|
import AvisList from '../../components/AvisList';
|
||||||
|
import useReviews from '../../hooks/reviews';
|
||||||
|
import styles from '../../styles/AvisListPage.module.css'
|
||||||
|
|
||||||
|
export default function AvisListPage() {
|
||||||
|
const [minGrade, setMinGrade] = useState(0);
|
||||||
|
const [maxGrade, setMaxGrade] = useState(10);
|
||||||
|
const [sources, setSources] = useState({'borne': true, 'website': true})
|
||||||
|
const [filteredReviews, setFilteredREviews] = useState([])
|
||||||
|
|
||||||
|
const {reviews, error, loading} = useReviews();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(reviews)
|
||||||
|
const newReviews = reviews.filter((review) => review.note_principale >= minGrade && review.note_principale <= maxGrade && sources[review.nom_source])
|
||||||
|
setFilteredREviews(newReviews)
|
||||||
|
}, [reviews, minGrade, maxGrade, sources])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(minGrade > maxGrade) {
|
||||||
|
setMinGrade(maxGrade);
|
||||||
|
}
|
||||||
|
}, [maxGrade]);
|
||||||
|
useEffect(() => {
|
||||||
|
if(minGrade > maxGrade) {
|
||||||
|
setMaxGrade(minGrade);
|
||||||
|
}
|
||||||
|
}, [minGrade])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container fluid>
|
||||||
|
<Card>
|
||||||
|
<Card.Header>Tous les avis</Card.Header>
|
||||||
|
<Card.Body>
|
||||||
|
<Row>
|
||||||
|
<Form>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Types d'avis</Form.Label>
|
||||||
|
<Form.Check
|
||||||
|
type="switch"
|
||||||
|
label="Borne"
|
||||||
|
onChange={(e) => setSources({...sources, 'borne': e.target.checked})}
|
||||||
|
checked={sources['borne']}
|
||||||
|
/>
|
||||||
|
<Form.Check
|
||||||
|
type="switch"
|
||||||
|
label="QR Code"
|
||||||
|
onChange={(e) => setSources({...sources, 'website': e.target.checked})}
|
||||||
|
checked={sources['website']}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Note</Form.Label>
|
||||||
|
<div className='d-flex flex-row justify-content-around col-md-6'>
|
||||||
|
<div>Min : {minGrade}/10</div>
|
||||||
|
<div className={styles.sliderContainer}>
|
||||||
|
<input type="range" value={minGrade} onChange={(e) => setMinGrade(e.target.value)} min="0" max="10" step="1" className={styles.slider}></input>
|
||||||
|
<input type="range" value={maxGrade} onChange={(e) => setMaxGrade(e.target.value)} min="0" max="10" step="1" className={styles.slider}></input>
|
||||||
|
</div>
|
||||||
|
<div>Max : {maxGrade}/10</div>
|
||||||
|
</div>
|
||||||
|
</Form.Group>
|
||||||
|
</Form>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
{!loading && !error && <AvisList avis={filteredReviews} />}
|
||||||
|
</Row>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Container >
|
||||||
|
)
|
||||||
|
}
|
||||||
76
code/interface_admin/pages/index.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import Head from 'next/head'
|
||||||
|
import { Card, Container } from 'react-bootstrap'
|
||||||
|
import ComparativeBarChart from '../components/ComparativeBarChart'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import styles from "../styles/Home.module.css"
|
||||||
|
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const [datasets, setDatasets] = useState(null);
|
||||||
|
const [averages, setAverages] = useState(null);
|
||||||
|
const [differences, setDifferences] = useState(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (datasets) {
|
||||||
|
let newAverages = []
|
||||||
|
let newDifferences = []
|
||||||
|
for (let i = 0; i < datasets.length; i++) {
|
||||||
|
newAverages[i] = datasets[i].current.reduce((a, b) => a + b) / datasets[i].current.length
|
||||||
|
newDifferences[i] = newAverages[i] - datasets[i].previous.reduce((a, b) => a + b) / datasets[i].previous.length
|
||||||
|
}
|
||||||
|
setAverages(newAverages);
|
||||||
|
setDifferences(newDifferences);
|
||||||
|
}
|
||||||
|
}, [datasets]);
|
||||||
|
|
||||||
|
useEffect(() => setDatasets([
|
||||||
|
{ title: "Nombre d'avis", current: [3, 2, 3, 4, 5, 6, 7], previous: [7, 6, 5, 4, 3, 2, 1] },
|
||||||
|
{ title: "Notes moyennes", current: [1, 2, 3, 4, 5, 6, 7], previous: [7, 6, 5, 4, 3, 2, 1] }
|
||||||
|
]), []);
|
||||||
|
function dataVisualizer(title, current, previous, average, difference) {
|
||||||
|
return <div key={title}>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
<Card className={styles.averageCard}>
|
||||||
|
<Card.Title>Moyenne</Card.Title>
|
||||||
|
<Card.Body className={styles.averageCardBody}>
|
||||||
|
<div
|
||||||
|
className={styles.averageMainValue}
|
||||||
|
>
|
||||||
|
{Math.round(average * 1e2) / 1e2}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={[styles.averageCardSecondaryValue, difference >= 0 ? styles.averagePositive : styles.averageNegative].join(' ')}
|
||||||
|
>
|
||||||
|
{(difference >= 0 ? "+" : "-") + Math.round(difference * 1e2) / 1e2}
|
||||||
|
</div>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
<ComparativeBarChart
|
||||||
|
xlabels={["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"]}
|
||||||
|
label0="Cette semaine"
|
||||||
|
label1="La semaine dernière"
|
||||||
|
data0={current}
|
||||||
|
data1={previous}
|
||||||
|
/>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Create Next App</title>
|
||||||
|
<meta name="description" content="Page d'accueil" />
|
||||||
|
</Head>
|
||||||
|
<Container fluid>
|
||||||
|
<Card>
|
||||||
|
<Card.Header as="h2">Vos performances cette semaine</Card.Header>
|
||||||
|
<Card.Body>
|
||||||
|
{datasets && averages && differences && datasets.map((set, i) => dataVisualizer(set.title, set.current, set.previous, averages[i], differences[i]))}
|
||||||
|
</Card.Body>
|
||||||
|
<div className='col col-12 col-lg-8 mx-auto'>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
7
code/interface_admin/pages/stats.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export default function Stats() {
|
||||||
|
return (
|
||||||
|
<div>stats</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
BIN
code/interface_admin/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
1
code/interface_admin/public/next.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
1
code/interface_admin/public/thirteen.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="31" fill="none"><g opacity=".9"><path fill="url(#a)" d="M13 .4v29.3H7V6.3h-.2L0 10.5V5L7.2.4H13Z"/><path fill="url(#b)" d="M28.8 30.1c-2.2 0-4-.3-5.7-1-1.7-.8-3-1.8-4-3.1a7.7 7.7 0 0 1-1.4-4.6h6.2c0 .8.3 1.4.7 2 .4.5 1 .9 1.7 1.2.7.3 1.6.4 2.5.4 1 0 1.7-.2 2.5-.5.7-.3 1.3-.8 1.7-1.4.4-.6.6-1.2.6-2s-.2-1.5-.7-2.1c-.4-.6-1-1-1.8-1.4-.8-.4-1.8-.5-2.9-.5h-2.7v-4.6h2.7a6 6 0 0 0 2.5-.5 4 4 0 0 0 1.7-1.3c.4-.6.6-1.3.6-2a3.5 3.5 0 0 0-2-3.3 5.6 5.6 0 0 0-4.5 0 4 4 0 0 0-1.7 1.2c-.4.6-.6 1.2-.6 2h-6c0-1.7.6-3.2 1.5-4.5 1-1.3 2.2-2.3 3.8-3C25 .4 26.8 0 28.8 0s3.8.4 5.3 1.1c1.5.7 2.7 1.7 3.6 3a7.2 7.2 0 0 1 1.2 4.2c0 1.6-.5 3-1.5 4a7 7 0 0 1-4 2.2v.2c2.2.3 3.8 1 5 2.2a6.4 6.4 0 0 1 1.6 4.6c0 1.7-.5 3.1-1.4 4.4a9.7 9.7 0 0 1-4 3.1c-1.7.8-3.7 1.1-5.8 1.1Z"/></g><defs><linearGradient id="a" x1="20" x2="20" y1="0" y2="30.1" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-color="#3D3D3D"/></linearGradient><linearGradient id="b" x1="20" x2="20" y1="0" y2="30.1" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-color="#3D3D3D"/></linearGradient></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
1
code/interface_admin/public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||||
|
After Width: | Height: | Size: 629 B |
5
code/interface_admin/styles/Avis.module.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.personIcon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
/* font-size: 50px; */
|
||||||
|
}
|
||||||
6
code/interface_admin/styles/AvisList.module.css
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
/* ==== TABLEAU ==== */
|
||||||
|
.row:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #EEE;
|
||||||
|
}
|
||||||
57
code/interface_admin/styles/AvisListPage.module.css
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/* ==== SLIDER ==== */
|
||||||
|
.sliderContainer {
|
||||||
|
position: relative;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliderContainer > input[type=range]::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
pointer-events: all;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 0 1px #C6C6C6;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliderContainer > input[type=range]::-moz-range-thumb {
|
||||||
|
z-index: 99;
|
||||||
|
pointer-events: all;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 0 1px #C6C6C6;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliderContainer > input[type=range]::-webkit-slider-thumb:hover {
|
||||||
|
background: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliderContainer >input[type=range]::-webkit-slider-thumb:active {
|
||||||
|
box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
|
||||||
|
-webkit-box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
|
||||||
|
}
|
||||||
|
.sliderContainer > input[type=range]::-moz-range-thumb:hover {
|
||||||
|
background: #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliderContainer >input[type=range]::-moz-range-thumb:active {
|
||||||
|
box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
|
||||||
|
-webkit-box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliderContainer >input[type="range"] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
height: 2px;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #C6C6C6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
29
code/interface_admin/styles/Home.module.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
.averageCard {
|
||||||
|
width: min-content;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.averageCardBody {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.averageMainValue {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.averageSecondaryValue {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.averagePositive {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.averageNegative {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
0
code/interface_admin/styles/globals.css
Normal file
7
code/interface_borne/assets/css/bootstrap-grid.min.css
vendored
Normal file
27
code/interface_borne/assets/css/main.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#camera > video, #camera > canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
text-align: center;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#camera > video {
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
#camera > canvas {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
BIN
code/interface_borne/assets/img/thumbs_down.png
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
code/interface_borne/assets/img/thumbs_up.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
11
code/interface_borne/assets/js/audio_page.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class AudioPage {
|
||||||
|
constructor() {
|
||||||
|
this.isEnabled = false;
|
||||||
|
this.DOMElement = document.getElementById("audio");
|
||||||
|
|
||||||
|
}
|
||||||
|
set enabled(isEnabled) {
|
||||||
|
this.isEnabled = isEnabled;
|
||||||
|
this.DOMElement.style.display = isEnabled ? "block" : "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
145
code/interface_borne/assets/js/camera_page.js
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
class CameraPage {
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
this.spinnerWeight = 10;
|
||||||
|
this.spinnerColor = "#0F0FFF"
|
||||||
|
|
||||||
|
this.canvas = document.getElementById("overlay-canvas");
|
||||||
|
this.ctx = this.canvas.getContext("2d");
|
||||||
|
this.video = document.getElementById("camera-video");
|
||||||
|
this.width;
|
||||||
|
this.height; //calcule automatiquement en fonction de la largeur du flux vidéo
|
||||||
|
this.videoWidth;
|
||||||
|
this.videoHeight;
|
||||||
|
this.streaming = false;
|
||||||
|
this.activeEffects = [];
|
||||||
|
this.images = {};
|
||||||
|
this._startup();
|
||||||
|
this._loadImages();
|
||||||
|
this._enabled = false;
|
||||||
|
this.DOMElement = document.getElementById("camera");
|
||||||
|
}
|
||||||
|
|
||||||
|
set enabled(val) {
|
||||||
|
this._enabled = val;
|
||||||
|
this.DOMElement.style.display = val ? "block" : "none";
|
||||||
|
if (val) {
|
||||||
|
this._frame();
|
||||||
|
this.video.play();
|
||||||
|
}else {
|
||||||
|
this.video.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get enabled() {
|
||||||
|
return this._enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
_startup() {
|
||||||
|
navigator.mediaDevices
|
||||||
|
.getUserMedia({ video: true, audio: false })
|
||||||
|
.then((stream) => {
|
||||||
|
this.video.srcObject = stream;
|
||||||
|
this.video.play();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(`Erreur pendant la lecture de la camera: ${err}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.video.addEventListener(
|
||||||
|
"canplay",
|
||||||
|
(ev) => {
|
||||||
|
if (!this.streaming) {
|
||||||
|
//calcul de la taille de la vidéo en fonction de la taille de la fenêtre pour qu'elle soit toujours visible
|
||||||
|
let aspectRatio = this.video.videoWidth / this.video.videoHeight;
|
||||||
|
if (window.innerHeight * aspectRatio > window.innerWidth) {
|
||||||
|
this.width = window.innerWidth;
|
||||||
|
this.height = window.innerWidth / aspectRatio;
|
||||||
|
} else {
|
||||||
|
this.width = window.innerHeight * aspectRatio;
|
||||||
|
this.height = window.innerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.videoHeight = this.video.videoHeight;
|
||||||
|
this.videoWidth = this.video.videoWidth;
|
||||||
|
|
||||||
|
this.video.setAttribute("width", this.width);
|
||||||
|
this.video.setAttribute("height", this.height);
|
||||||
|
this.canvas.setAttribute("width", this.width);
|
||||||
|
this.canvas.setAttribute("height", this.height);
|
||||||
|
this.streaming = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadImages() {
|
||||||
|
this.images.thumbsUp = new Image();
|
||||||
|
this.images.thumbsUp.src = "assets/img/thumbs_up.png";
|
||||||
|
this.images.thumbsDown = new Image();
|
||||||
|
this.images.thumbsDown.src = "assets/img/thumbs_down.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
_frame() {
|
||||||
|
if (this.streaming && this.enabled && this.width && this.height) {
|
||||||
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
// this.ctx.drawImage(this.video, 0, 0, this.width, this.height);
|
||||||
|
this._drawEffects();
|
||||||
|
}
|
||||||
|
if (this.enabled) {
|
||||||
|
requestAnimationFrame(() => this._frame());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_scaleEffect(x, y, width, height) {
|
||||||
|
let xScale = this.width / this.videoWidth;
|
||||||
|
let yScale = this.height / this.videoHeight;
|
||||||
|
return {
|
||||||
|
x: x * xScale,
|
||||||
|
y: y * yScale,
|
||||||
|
width: width * xScale,
|
||||||
|
height: height * yScale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawEffects() {
|
||||||
|
for (let effect of this.activeEffects) {
|
||||||
|
let { x, y, width, height } = this._scaleEffect(effect.x, effect.y, effect.width, effect.height);
|
||||||
|
if (effect.type == "thumbs_down") {
|
||||||
|
this._drawThumbsDown(x, y, width, height);
|
||||||
|
}
|
||||||
|
if (effect.type == "thumbs_up") {
|
||||||
|
this._drawThumbsUp(x, y, width, height);
|
||||||
|
}
|
||||||
|
if (effect.type == "loading") {
|
||||||
|
this._drawLoading(x, y, width, effect.progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawLoading(x, y, radius, progress) {
|
||||||
|
this.ctx.lineWidth = this.spinnerWeight;
|
||||||
|
this.ctx.strokeStyle = this.spinnerColor;
|
||||||
|
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.arc(x, y, radius, 0, progress * 2 * Math.PI);
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawThumbsDown(x, y, width, height) {
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.drawImage(this.images.thumbsDown, x, y, width, height);
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawThumbsUp(x, y, width, height) {
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.drawImage(this.images.thumbsUp, x, y, width, height);
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
setEffects(effects) {
|
||||||
|
this.activeEffects = effects;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
code/interface_borne/assets/js/main.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
let stateManager;
|
||||||
|
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
stateManager = new StateManager();
|
||||||
|
}, false);
|
||||||
16
code/interface_borne/assets/js/network.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
class WebsocketClient {
|
||||||
|
constructor(onNewEffects, onNewState) {
|
||||||
|
this.socket = new WebSocket("ws://localhost:5000");
|
||||||
|
this.socket.addEventListener("open", (event) => {
|
||||||
|
this.socket.send("connected");
|
||||||
|
});
|
||||||
|
this.socket.addEventListener("message", (event) => {
|
||||||
|
let msg = JSON.parse(event.data);
|
||||||
|
if (msg.type == "effects") {
|
||||||
|
onNewEffects(msg.effects);
|
||||||
|
}else if(msg.type == "state") {
|
||||||
|
onNewState(msg.state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
12
code/interface_borne/assets/js/sleeping_page.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
class SleepingPage {
|
||||||
|
constructor(onWakeUp) {
|
||||||
|
this.onWakeUp = onWakeUp;
|
||||||
|
this.isEnabled = false;
|
||||||
|
this.DOMElement = document.getElementById("sleeping-page");
|
||||||
|
|
||||||
|
}
|
||||||
|
set enabled(isEnabled) {
|
||||||
|
this.isEnabled = isEnabled;
|
||||||
|
this.DOMElement.style.display = isEnabled ? "block" : "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
49
code/interface_borne/assets/js/state_manager.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
const STATE = {
|
||||||
|
sleeping: 0,
|
||||||
|
video: 1,
|
||||||
|
audio: 2,
|
||||||
|
thankYou: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
class StateManager {
|
||||||
|
constructor() {
|
||||||
|
this._state = STATE.sleeping;
|
||||||
|
this._cameraPage = new CameraPage();
|
||||||
|
this._sleepingPage = new SleepingPage();
|
||||||
|
this._audioPage = new AudioPage();
|
||||||
|
this._thankYouPage = new ThankYouPage();
|
||||||
|
|
||||||
|
this.wsClient = new WebsocketClient(
|
||||||
|
(effects) => this._cameraPage.setEffects(effects),
|
||||||
|
(state) => this.changeState(state)
|
||||||
|
);
|
||||||
|
|
||||||
|
this._sleepingPage.enabled = true;
|
||||||
|
this._cameraPage.enabled = false;
|
||||||
|
this._audioPage.enabled = false;
|
||||||
|
this._thankYouPage.enabled = false;
|
||||||
|
//TODO: Remove qd implémenté dans le backend
|
||||||
|
document.getElementById("sleeping-page-continue").onclick = () => this.setState(STATE.video);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(newState) {
|
||||||
|
console.log({current:this._state,new:newState})
|
||||||
|
if(this._state == STATE.sleeping && newState == STATE.video) {
|
||||||
|
this._cameraPage.enabled = true;
|
||||||
|
this._sleepingPage.enabled = false;
|
||||||
|
this._state = newState;
|
||||||
|
}else if(this._state == STATE.video && newState == STATE.audio) {
|
||||||
|
this._cameraPage.enabled = false;
|
||||||
|
this._audioPage.enabled = true;
|
||||||
|
this._state = newState;
|
||||||
|
}else if(this._state == STATE.audio && newState == STATE.thankYou) {
|
||||||
|
this._audioPage.enabled = false;
|
||||||
|
this._thankYouPage.enabled = true;
|
||||||
|
this._state = newState;
|
||||||
|
}else if(this._state == STATE.thankYou && newState == STATE.sleeping) {
|
||||||
|
this._thankYouPage.enabled = false;
|
||||||
|
this._sleepingPage.enabled = true;
|
||||||
|
this._state = newState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
code/interface_borne/assets/js/thank_you_page.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class ThankYouPage {
|
||||||
|
constructor() {
|
||||||
|
this.isEnabled = false;
|
||||||
|
this.DOMElement = document.getElementById("thank-you");
|
||||||
|
}
|
||||||
|
|
||||||
|
set enabled(isEnabled) {
|
||||||
|
this.isEnabled = isEnabled;
|
||||||
|
this.DOMElement.style.display = isEnabled ? "block" : "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
41
code/interface_borne/index.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="assets/css/main.css">
|
||||||
|
<!-- <link rel="stylesheet" href="assets/css/bootstrap-grid.min.css"> -->
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
|
||||||
|
<title>Téléreview</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="sleeping-page" class="page">
|
||||||
|
<h1>Venez donner votre avis</h1>
|
||||||
|
<!-- Boutton pour test -->
|
||||||
|
<button id="sleeping-page-continue">Continuer</button>
|
||||||
|
</div>
|
||||||
|
<div id="camera">
|
||||||
|
<canvas id="overlay-canvas"></canvas>
|
||||||
|
<video id="camera-video"></video>
|
||||||
|
</div>
|
||||||
|
<div id="audio">
|
||||||
|
<h1>Enregistrement audio blabal</h1>
|
||||||
|
</div>
|
||||||
|
<div id="thank-you">
|
||||||
|
<h1>Merci pour votre avis !!!!!!!!!!</h1>
|
||||||
|
</div>
|
||||||
|
<script src="assets/js/camera_page.js"></script>
|
||||||
|
<script src="assets/js/network.js"></script>
|
||||||
|
<script src="assets/js/thank_you_page.js"></script>
|
||||||
|
<script src="assets/js/audio_page.js"></script>
|
||||||
|
<script src="assets/js/sleeping_page.js"></script>
|
||||||
|
<script src="assets/js/state_manager.js"></script>
|
||||||
|
<script src="assets/js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
24
code/reviews_api/.dockerignore
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
15
code/reviews_api/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
FROM node:lts-alpine
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
#installation des dépendances
|
||||||
|
COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"]
|
||||||
|
RUN npm install --production --silent && mv node_modules ../
|
||||||
|
|
||||||
|
#On copie le code source
|
||||||
|
COPY . .
|
||||||
|
#On change le propriétaire du dossier
|
||||||
|
RUN chown -R node /usr/src/app
|
||||||
|
#On change l'utilisateur
|
||||||
|
USER node
|
||||||
|
|
||||||
|
CMD ["node", "index.js"]
|
||||||
@@ -1,31 +1,35 @@
|
|||||||
# Installation
|
# Serveur de traitement des données
|
||||||
* Pour faire fonctinoner le serveur sur vos machines il y a 3 choses a faire
|
Ce serveur s'occupe de fournir une API web pour pourvoir ajouter des avis sur la borne, réxupérer des avis, calculer et fournir les statistiques sur ces avis
|
||||||
1. Installer node js : https://nodejs.org/en/download/
|
|
||||||
2. Ouvrir un terminal et aller dans ce dossier (code/server) et tapper `npm install` pour installer les pacakges nécessaires
|
# Installation (Si vous voulez le lancer hors du container docker)
|
||||||
3. copier le fichier `.env_template` et le nommer `.env` et remplir les variables (cela est fait pour le pas mettre les mots de passes sur le gitlab, faites attention de ne jamais commit le fichier .env !!)
|
* Pour faire fonctinoner le serveur sur vos machines il y a 3 choses a faire
|
||||||
4. pour lancer le serveur faire `node index.js`
|
1. Installer node js : https://nodejs.org/en/download/
|
||||||
|
2. Ouvrir un terminal et aller dans ce dossier (code/server) et tapper `npm install` pour installer les pacakges nécessaires
|
||||||
# Utilisation
|
3. copier le fichier `.env_template` et le nommer `.env` et remplir les variables (cela est fait pour le pas mettre les mots de passes sur le gitlab, faites attention de ne jamais commit le fichier .env !!)
|
||||||
## Avis laissés sur la borne (hors réseaux sociaux)
|
4. pour lancer le serveur faire `node index.js`
|
||||||
### Routes GET :
|
|
||||||
- `/borne/get_last_reviews?limit=LIM` : renvoie les LIM derniers avis sur la borne
|
# Utilisation
|
||||||
- `/borne/get_review?id=ID` : renvoie les infos sur l'avis d'in ID
|
## Avis laissés sur la borne (hors réseaux sociaux)
|
||||||
- `/borne/get_criteres` : renvoie les criteres de notations valide pour les notes autres
|
### Routes GET :
|
||||||
- `/borne/notes_autres?critere=CRIT&limit=LIM` : renvoie les LIM dernières notes sur le critère CRIT
|
- `/borne/get_last_reviews?limit=LIM` : renvoie les LIM derniers avis sur la borne
|
||||||
- `/borne/notes_autres?id=ID&limit=LIM` : renvoie toutes les notes spécifiques liées à l'avis ID
|
- `/borne/get_review?id=ID` : renvoie les infos sur l'avis d'in ID
|
||||||
|
- `/borne/get_criteres` : renvoie les criteres de notations valide pour les notes autres
|
||||||
### Routes POST
|
- `/borne/notes_autres?critere=CRIT&limit=LIM` : renvoie les LIM dernières notes sur le critère CRIT
|
||||||
- `/add_review` : Ajoute une review et un auteur, paramètres POST :
|
- `/borne/notes_autres?id=ID&limit=LIM` : renvoie toutes les notes spécifiques liées à l'avis ID
|
||||||
* [OBLIGATOIRE] `note` : note principale de la review entre 0 et 10 compris
|
- `get_stats?interval=INTERVAL&limit=LIM`: interval est "jour", "mois", "annee", "semaine", l'interval de calcul de données demandé, LIM est le nombre des stats à récupérer
|
||||||
* [OBLIGATOIRE] `source` : nom de la source de l'avis, doit être `borne` ou `website` pour resp la borne et le site
|
|
||||||
* `auteur_age` : age de l'auteur
|
### Routes POST
|
||||||
* `auteur_sexe` : sexe de l'auteur (valeurs valide 'f', 'h', 'a')
|
- `/add_review` : Ajoute une review et un auteur, paramètres POST :
|
||||||
* `commentaire` : Commentaire laissé avec l'avis
|
* [OBLIGATOIRE] `note` : note principale de la review entre 0 et 10 compris
|
||||||
* `notes autres` : sous la forme
|
* [OBLIGATOIRE] `source` : nom de la source de l'avis, doit être `borne` ou `website` pour resp la borne et le site
|
||||||
```json
|
* `auteur_age` : age de l'auteur
|
||||||
{
|
* `auteur_sexe` : sexe de l'auteur (valeurs valide 'f', 'h', 'a')
|
||||||
"critere1": 8,
|
* `commentaire` : Commentaire laissé avec l'avis
|
||||||
"critere2": 2,
|
* `notes autres` : sous la forme
|
||||||
"critere3": 0
|
```json
|
||||||
}
|
{
|
||||||
```
|
"critere1": 8,
|
||||||
|
"critere2": 2,
|
||||||
|
"critere3": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -1,200 +1,207 @@
|
|||||||
import conn from '../database.js';
|
import conn from '../database.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renvoie les derniers avis laissés sur la borne trié par ordre chronologque décroissant
|
* Renvoie les derniers avis laissés sur la borne trié par ordre chronologque décroissant
|
||||||
* @param {Number} limit Nombre d'avis a afficher
|
* @param {Number} limit Nombre d'avis a afficher
|
||||||
* @returns Une liste d'objets de la forme {id: id de l'avis, date: date de l'avis, note_principale: note sur 10, commentaire: avis textuel, nom_source, sexe_auteur, age_auteur}
|
* @returns Une liste d'objets de la forme {id: id de l'avis, date: date de l'avis, note_principale: note sur 10, commentaire: avis textuel, nom_source, sexe_auteur, age_auteur}
|
||||||
*/
|
*/
|
||||||
const getLastReviews = (limit=10) => {
|
const getLastReviews = (limit = 10) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let sql = `SELECT borne_avis.id,date,note_principale,commentaire,sources.nom as nom_source, borne_auteurs.sexe as sexe_auteur, borne_auteurs.age as age_auteur
|
let sql = `SELECT borne_avis.id,date,note_principale,commentaire,sources.nom as nom_source, borne_auteurs.sexe as sexe_auteur, borne_auteurs.age as age_auteur
|
||||||
FROM borne_avis
|
FROM borne_avis
|
||||||
JOIN sources ON sources.id = source_id
|
JOIN sources ON sources.id = source_id
|
||||||
JOIN borne_auteurs ON borne_auteurs.id = id_auteur
|
JOIN borne_auteurs ON borne_auteurs.id = id_auteur
|
||||||
ORDER BY borne_avis.id DESC LIMIT ?`;
|
ORDER BY borne_avis.id DESC LIMIT ?`;
|
||||||
conn.query(sql, [limit], (err, res) => {
|
conn.query(sql, [limit], (err, res) => {
|
||||||
if(err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}else {
|
} else {
|
||||||
resolve(res)
|
resolve(res)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renvoie les informations sur un avis avec un ID spécifique
|
* Renvoie les informations sur un avis avec un ID spécifique
|
||||||
* @param {Number} id Id de la review
|
* @param {Number} id Id de la review
|
||||||
* @returns Un objet de la forme des objets dans la table borne_avis ayant l'id id s'il existe, renvoie une erreur sinon
|
* @returns Un objet de la forme des objets dans la table borne_avis ayant l'id id s'il existe, renvoie une erreur sinon
|
||||||
*/
|
*/
|
||||||
const getReviewFromId = (id) => {
|
const getReviewFromId = (id) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let sql = `SELECT * FROM borne_avis WHERE id = ? LIMIT 1`;
|
let sql = `SELECT borne_avis.id,date,note_principale,commentaire,sources.nom as nom_source, borne_auteurs.sexe as sexe_auteur, borne_auteurs.age as age_auteur
|
||||||
conn.query(sql, [id], (err, res) => {
|
FROM borne_avis
|
||||||
if(err) {
|
JOIN sources ON sources.id = source_id
|
||||||
reject(err);
|
JOIN borne_auteurs ON borne_auteurs.id = id_auteur
|
||||||
}else {
|
WHERE borne_avis.id = ?
|
||||||
if(res.length != 1) {
|
LIMIT 1`;
|
||||||
reject(new Error("Avis avec cet ID non trouvé"))
|
conn.query(sql, [id], (err, res) => {
|
||||||
}else {
|
if (err) {
|
||||||
resolve(res[0])
|
reject(err);
|
||||||
}
|
} else {
|
||||||
if(res.length != 1) {
|
if (res.length != 1) {
|
||||||
reject(new Error("Avis avec cet ID non trouvé"))
|
reject(new Error("Avis avec cet ID non trouvé"))
|
||||||
}else {
|
} else {
|
||||||
resolve(res[0])
|
resolve(res[0])
|
||||||
}
|
}
|
||||||
}
|
if (res.length != 1) {
|
||||||
})
|
reject(new Error("Avis avec cet ID non trouvé"))
|
||||||
})
|
} else {
|
||||||
}
|
resolve(res[0])
|
||||||
|
}
|
||||||
/**
|
}
|
||||||
* Renvoie tout les critères de notation valides
|
})
|
||||||
* @returns Une liste d'objets de la forme {id,nom}
|
})
|
||||||
*/
|
}
|
||||||
const getCriteres = () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
/**
|
||||||
let sql = `SELECT * FROM borne_criteres`;
|
* Renvoie tout les critères de notation valides
|
||||||
conn.query(sql, [limit], (err, res) => {
|
* @returns Une liste d'objets de la forme {id,nom}
|
||||||
if(err) {
|
*/
|
||||||
reject(err);
|
const getCriteres = () => {
|
||||||
}else {
|
return new Promise((resolve, reject) => {
|
||||||
resolve(res)
|
let sql = `SELECT * FROM borne_criteres`;
|
||||||
}
|
conn.query(sql, [limit], (err, res) => {
|
||||||
})
|
if (err) {
|
||||||
})
|
reject(err);
|
||||||
}
|
} else {
|
||||||
|
resolve(res)
|
||||||
/**
|
}
|
||||||
* Renvoie les "limit" notes les plus récentes laissées pour un critère spécifié
|
})
|
||||||
* @param {String} critere Nom de critère
|
})
|
||||||
* @param {Number} limit Nombre max de note a afficher
|
}
|
||||||
* @returns une liste d'objets de la forme {id,date,critere,note,avis_id:id de l'avis lié a cette note}
|
|
||||||
*/
|
/**
|
||||||
const getNotesAutresFromCritere = (critere,limit=10) => {
|
* Renvoie les "limit" notes les plus récentes laissées pour un critère spécifié
|
||||||
return new Promise((resolve, reject) => {
|
* @param {String} critere Nom de critère
|
||||||
let sql = `SELECT borne_notes_autre.id as id,date,borne_criteres.nom as critere, note, avis_id
|
* @param {Number} limit Nombre max de note a afficher
|
||||||
FROM borne_notes_autre
|
* @returns une liste d'objets de la forme {id,date,critere,note,avis_id:id de l'avis lié a cette note}
|
||||||
JOIN borne_criteres ON borne_criteres.id = critere_id
|
*/
|
||||||
WHERE borne_criteres.nom = ?
|
const getNotesAutresFromCritere = (critere, limit = 10) => {
|
||||||
ORDER BY borne_notes_autre.id DESC LIMIT ? ;`;
|
return new Promise((resolve, reject) => {
|
||||||
conn.query(sql, [critere,limit], (err, res) => {
|
let sql = `SELECT borne_notes_autre.id as id,date,borne_criteres.nom as critere, note, avis_id
|
||||||
if(err) {
|
FROM borne_notes_autre
|
||||||
reject(err);
|
JOIN borne_criteres ON borne_criteres.id = critere_id
|
||||||
}else {
|
WHERE borne_criteres.nom = ?
|
||||||
resolve(res)
|
ORDER BY borne_notes_autre.id DESC LIMIT ? ;`;
|
||||||
}
|
conn.query(sql, [critere, limit], (err, res) => {
|
||||||
})
|
if (err) {
|
||||||
})
|
reject(err);
|
||||||
}
|
} else {
|
||||||
|
resolve(res)
|
||||||
/**
|
}
|
||||||
* Renvoie toutes les notes sur des critères spécifiques laissée pour un avis spécifique
|
})
|
||||||
* @param {Number} reviewId Id de l'avis
|
})
|
||||||
* @returns une liste d'objets de la forme {id,critere:nom du critère, note:note sur 10}
|
}
|
||||||
*/
|
|
||||||
const getNotesAutresFromReview = (reviewId) => {
|
/**
|
||||||
return new Promise((resolve, reject) => {
|
* Renvoie toutes les notes sur des critères spécifiques laissée pour un avis spécifique
|
||||||
let sql = `SELECT borne_notes_autre.id as id,borne_criteres.nom as critere, note
|
* @param {Number} reviewId Id de l'avis
|
||||||
FROM borne_notes_autre
|
* @returns une liste d'objets de la forme {id,critere:nom du critère, note:note sur 10}
|
||||||
JOIN borne_criteres on borne_criteres.id = critere_id
|
*/
|
||||||
WHERE avis_id = ?
|
const getNotesAutresFromReview = (reviewId) => {
|
||||||
ORDER BY borne_notes_autre.id DESC`;
|
return new Promise((resolve, reject) => {
|
||||||
conn.query(sql, [reviewId], (err, res) => {
|
let sql = `SELECT borne_notes_autre.id as id,borne_criteres.nom as critere, note
|
||||||
if(err) {
|
FROM borne_notes_autre
|
||||||
reject(err);
|
JOIN borne_criteres on borne_criteres.id = critere_id
|
||||||
}else {
|
WHERE avis_id = ?
|
||||||
resolve(res)
|
ORDER BY borne_notes_autre.id DESC`;
|
||||||
}
|
conn.query(sql, [reviewId], (err, res) => {
|
||||||
})
|
if (err) {
|
||||||
})
|
reject(err);
|
||||||
}
|
} else {
|
||||||
|
resolve(res)
|
||||||
/**
|
}
|
||||||
* Renvoie les dernières statistiques
|
})
|
||||||
* @param {String} interval Interval de temps de la statistique, valeurs possibles : "jour","mois","semaine","annee"
|
})
|
||||||
* @param {Number} limit Nombre max de stats a renvoyer
|
}
|
||||||
* @returns Une liste d'objet ou chaque objet correspond a une stat sur une periode donnée (par ex une stat hebdo datée du 07/01 correspond a une stat sur la semaine du 01/01 au 07/01), ces objets sont de la forme
|
|
||||||
*/
|
/**
|
||||||
const getStats = (interval, limit=10) => {
|
* Renvoie les dernières statistiques
|
||||||
return new Promise((resolve, reject) => {
|
* @param {String} interval Interval de temps de la statistique, valeurs possibles : "jour","mois","semaine","annee"
|
||||||
if(!["jour","mois","semaine","annee"].includes(interval)) {
|
* @param {Number} limit Nombre max de stats a renvoyer
|
||||||
reject(new Error("Invalid time interval"));
|
* @returns Une liste d'objet ou chaque objet correspond a une stat sur une periode donnée (par ex une stat hebdo datée du 07/01 correspond a une stat sur la semaine du 01/01 au 07/01), ces objets sont de la forme
|
||||||
return;
|
*/
|
||||||
}
|
const getStats = (interval, limit = 10) => {
|
||||||
let sql = `SELECT * FROM stats_general_${interval} ORDER BY id DESC LIMIT ?;`;
|
return new Promise((resolve, reject) => {
|
||||||
conn.query(sql, [limit], (err, res) => {
|
if (!["jour", "mois", "semaine", "annee"].includes(interval)) {
|
||||||
if(err) {
|
reject(new Error("Invalid time interval"));
|
||||||
reject(err);
|
return;
|
||||||
}else {
|
}
|
||||||
resolve(res)
|
let sql = `SELECT * FROM stats_general_${interval} ORDER BY id DESC LIMIT ?;`;
|
||||||
}
|
conn.query(sql, [limit], (err, res) => {
|
||||||
})
|
if (err) {
|
||||||
})
|
reject(err);
|
||||||
}
|
} else {
|
||||||
|
resolve(res)
|
||||||
/*
|
}
|
||||||
Ces fonction sont des handlers pour les routes express, elles sont appelées par les routes et renvoient les données au format JSON
|
})
|
||||||
*/
|
})
|
||||||
export const handleGetLastReviews = (req, res) => {
|
}
|
||||||
getLastReviews(req.query.limit)
|
|
||||||
.then((reviews) => {
|
/*
|
||||||
res.send(reviews);
|
Ces fonction sont des handlers pour les routes express, elles sont appelées par les routes et renvoient les données au format JSON
|
||||||
})
|
*/
|
||||||
.catch((err) => {
|
export const handleGetLastReviews = (req, res) => {
|
||||||
res.status(500).send("Error: " + err.message);
|
getLastReviews(req.query.limit)
|
||||||
});
|
.then((reviews) => {
|
||||||
}
|
res.send(reviews);
|
||||||
|
})
|
||||||
export const handleGetReview = (req, res) => {
|
.catch((err) => {
|
||||||
getReviewFromId(req.query.id)
|
res.status(500).send("Error: " + err.message);
|
||||||
.then((review) => {
|
});
|
||||||
res.send(review);
|
}
|
||||||
})
|
|
||||||
.catch((err) => {
|
export const handleGetReview = (req, res) => {
|
||||||
res.status(500).send("Error: " + err.message);
|
getReviewFromId(req.query.id)
|
||||||
});
|
.then((review) => {
|
||||||
}
|
getNotesAutresFromReview(req.query.id).then((notesAutres) => {
|
||||||
|
res.send({ ...review, notes_autres: notesAutres });
|
||||||
export const handleGetCriteres = (req, res) => {
|
})
|
||||||
getCriteres()
|
})
|
||||||
.then((criteres) => {
|
.catch((err) => {
|
||||||
res.send(criteres);
|
res.status(500).send("Error: " + err.message);
|
||||||
})
|
});
|
||||||
.catch((err) => {
|
}
|
||||||
res.status(500).send("Error: " + err.message);
|
|
||||||
});
|
export const handleGetCriteres = (req, res) => {
|
||||||
}
|
getCriteres()
|
||||||
|
.then((criteres) => {
|
||||||
export const handleGetNotesAutres = (req, res) => {
|
res.send(criteres);
|
||||||
if(req.query.critere) {
|
})
|
||||||
getNotesAutresFromCritere(req.query.critere, req.query.limit)
|
.catch((err) => {
|
||||||
.then((notes) => {
|
res.status(500).send("Error: " + err.message);
|
||||||
res.send(notes);
|
});
|
||||||
})
|
}
|
||||||
.catch((err) => {
|
|
||||||
res.status(500).send("Error: " + err.message);
|
export const handleGetNotesAutres = (req, res) => {
|
||||||
});
|
if (req.query.critere) {
|
||||||
}else if(req.query.id) {
|
getNotesAutresFromCritere(req.query.critere, req.query.limit)
|
||||||
getNotesAutresFromReview(req.query.id)
|
.then((notes) => {
|
||||||
.then((notes) => {
|
res.send(notes);
|
||||||
res.send(notes);
|
})
|
||||||
})
|
.catch((err) => {
|
||||||
.catch((err) => {
|
res.status(500).send("Error: " + err.message);
|
||||||
res.status(500).send("Error: " + err.message);
|
});
|
||||||
});
|
} else if (req.query.id) {
|
||||||
}else {
|
getNotesAutresFromReview(req.query.id)
|
||||||
res.status(500).send("Error: no critere or id specified");
|
.then((notes) => {
|
||||||
}
|
res.send(notes);
|
||||||
}
|
})
|
||||||
|
.catch((err) => {
|
||||||
export const handleGetStats = (req, res) => {
|
res.status(500).send("Error: " + err.message);
|
||||||
getStats(req.query.interval, req.query.limit)
|
});
|
||||||
.then((stats) => {
|
} else {
|
||||||
res.send(stats);
|
res.status(500).send("Error: no critere or id specified");
|
||||||
})
|
}
|
||||||
.catch((err) => {
|
}
|
||||||
res.status(500).send("Error: " + err.message);
|
|
||||||
});
|
export const handleGetStats = (req, res) => {
|
||||||
|
getStats(req.query.interval, req.query.limit)
|
||||||
|
.then((stats) => {
|
||||||
|
res.send(stats);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
res.status(500).send("Error: " + err.message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -1,84 +1,84 @@
|
|||||||
import { Auteur, Review } from './structures.js';
|
import { Auteur, Review } from './structures.js';
|
||||||
import conn from '../database.js';
|
import conn from '../database.js';
|
||||||
import {getSourceId} from '../utils.js';
|
import {getSourceId} from '../utils.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ajoute un nouvel auteur de commentaire a la BDD
|
* Ajoute un nouvel auteur de commentaire a la BDD
|
||||||
* @param {Auteur} author L'auteur a ajouter
|
* @param {Auteur} author L'auteur a ajouter
|
||||||
* @returns une Promise qui renvoie l'id de l'utilisateur
|
* @returns une Promise qui renvoie l'id de l'utilisateur
|
||||||
*/
|
*/
|
||||||
const addAuteur = (author) => {
|
const addAuteur = (author) => {
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
const sql = "INSERT INTO borne_auteurs (age, sexe) VALUES (?);"
|
const sql = "INSERT INTO borne_auteurs (age, sexe) VALUES (?);"
|
||||||
conn.query(sql, [[author.age, author.sexe]], (err, res) => {
|
conn.query(sql, [[author.age, author.sexe]], (err, res) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
}else {
|
}else {
|
||||||
resolve(res.insertId);
|
resolve(res.insertId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Ajoute une note sur un critère spécifique dans la BDD
|
//Ajoute une note sur un critère spécifique dans la BDD
|
||||||
const addSpecificRating = (reviewId, label, value) => {
|
const addSpecificRating = (reviewId, label, value) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const sql = "INSERT INTO borne_notes_autre(critere_id, avis_id, note) VALUES ((SELECT id FROM borne_criteres WHERE borne_criteres.nom = ?), ?, ?)"
|
const sql = "INSERT INTO borne_notes_autre(critere_id, avis_id, note) VALUES ((SELECT id FROM borne_criteres WHERE borne_criteres.nom = ?), ?, ?)"
|
||||||
conn.query(sql, [label,reviewId, value], (err, res) => {
|
conn.query(sql, [label,reviewId, value], (err, res) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}else {
|
}else {
|
||||||
resolve(res.insertId);
|
resolve(res.insertId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ajoute un avis a la base de donnée
|
* Ajoute un avis a la base de donnée
|
||||||
* @param {Review} review la review a ajouter
|
* @param {Review} review la review a ajouter
|
||||||
* @param {Number} authorId l'ID de l'auteur de l'avis dans la BDD
|
* @param {Number} authorId l'ID de l'auteur de l'avis dans la BDD
|
||||||
* @param {Number} sourceId l'ID de la source de l'avis dans la BDD
|
* @param {Number} sourceId l'ID de la source de l'avis dans la BDD
|
||||||
* @returns une Promise qui renvoie l'id de l'avis
|
* @returns une Promise qui renvoie l'id de l'avis
|
||||||
*/
|
*/
|
||||||
const addReview = (review, authorId, sourceId) => {
|
const addReview = (review, authorId, sourceId) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const sql = "INSERT INTO borne_avis (id_auteur, note_principale, commentaire, source_id) VALUES (?);"
|
const sql = "INSERT INTO borne_avis (id_auteur, note_principale, commentaire, source_id) VALUES (?);"
|
||||||
conn.query(sql, [[authorId, review.note, review.commentaire, sourceId]], (err, res) => {
|
conn.query(sql, [[authorId, review.note, review.commentaire, sourceId]], (err, res) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
}else {
|
}else {
|
||||||
resolve(res.insertId);
|
resolve(res.insertId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Traite une requête POST qui contient les valeurs nécessaires pour ajouter un nouvel avis dans la BDD
|
* Traite une requête POST qui contient les valeurs nécessaires pour ajouter un nouvel avis dans la BDD
|
||||||
* Essaie d'ajouter l'avis et l'auteur dans la BDD, sinon renvoie l'erreur avec un code 500
|
* Essaie d'ajouter l'avis et l'auteur dans la BDD, sinon renvoie l'erreur avec un code 500
|
||||||
* @param {*} req requete
|
* @param {*} req requete
|
||||||
* @param {*} res reponse
|
* @param {*} res reponse
|
||||||
*/
|
*/
|
||||||
export const addReviewFromRequest = async (req,res) => {
|
export const addReviewFromRequest = async (req,res) => {
|
||||||
try {
|
try {
|
||||||
let notes_autre = {}
|
let notes_autre = {}
|
||||||
try{
|
try{
|
||||||
notes_autre = JSON.parse(req.body.notes_autre);
|
notes_autre = JSON.parse(req.body.notes_autre);
|
||||||
}catch(err){};
|
}catch(err){};
|
||||||
|
|
||||||
const author = new Auteur(req.body.auteur_age,req.body.auteur_sexe);
|
const author = new Auteur(req.body.auteur_age,req.body.auteur_sexe);
|
||||||
const review = new Review(author, req.body.note, req.body.source, req.body.commentaire, notes_autre)
|
const review = new Review(author, req.body.note, req.body.source, req.body.commentaire, notes_autre)
|
||||||
let authorId = await addAuteur(author);
|
let authorId = await addAuteur(author);
|
||||||
let sourceId = await getSourceId(review.source);
|
let sourceId = await getSourceId(review.source);
|
||||||
let reviewId = await addReview(review, authorId, sourceId );
|
let reviewId = await addReview(review, authorId, sourceId );
|
||||||
for(let label in review.notesAutre) {
|
for(let label in review.notesAutre) {
|
||||||
await addSpecificRating(reviewId, label, review.notesAutre[label]);
|
await addSpecificRating(reviewId, label, review.notesAutre[label]);
|
||||||
}
|
}
|
||||||
res.send("success")
|
res.send("success")
|
||||||
}catch(err) {
|
}catch(err) {
|
||||||
res.status(500).send("Error : " + err.message)
|
res.status(500).send("Error : " + err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,52 +1,52 @@
|
|||||||
//Liste des valeurs valides pour le sexe d'un utilisateur
|
//Liste des valeurs valides pour le sexe d'un utilisateur
|
||||||
const validSexes = ["h","f","a"];
|
const validSexes = ["h","f","a"];
|
||||||
|
|
||||||
//Classe pour représenter un avis laissé sur la borne que ce soit par l'interface web ou par un geste
|
//Classe pour représenter un avis laissé sur la borne que ce soit par l'interface web ou par un geste
|
||||||
export class Review {
|
export class Review {
|
||||||
/**
|
/**
|
||||||
* Constructeur
|
* Constructeur
|
||||||
* @param {Auteur} auteur L'auteur de l'avis
|
* @param {Auteur} auteur L'auteur de l'avis
|
||||||
* @param {Number} note La note principale entre 0 et 10 compris
|
* @param {Number} note La note principale entre 0 et 10 compris
|
||||||
* @param {String} source La source de l'avis, doit être une entrée dans la table soruces de la bdd
|
* @param {String} source La source de l'avis, doit être une entrée dans la table soruces de la bdd
|
||||||
* @param {String} commentaire Le commentaire lié a l'avis
|
* @param {String} commentaire Le commentaire lié a l'avis
|
||||||
* @param {Object} notesAutre Les notes secondaires laissées, sous la forme d'un object ou les clés sont les labels de chaque notes et les valeurs sont des notes entre 0 et 10 compris
|
* @param {Object} notesAutre Les notes secondaires laissées, sous la forme d'un object ou les clés sont les labels de chaque notes et les valeurs sont des notes entre 0 et 10 compris
|
||||||
*/
|
*/
|
||||||
constructor(auteur, note, source, commentaire=null, notesAutre={}) {
|
constructor(auteur, note, source, commentaire=null, notesAutre={}) {
|
||||||
this.auteur = auteur;
|
this.auteur = auteur;
|
||||||
this.note = note;
|
this.note = note;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.commentaire = commentaire;
|
this.commentaire = commentaire;
|
||||||
this.notesAutre = notesAutre;
|
this.notesAutre = notesAutre;
|
||||||
|
|
||||||
//On vérifie si toutes les données sont correctes
|
//On vérifie si toutes les données sont correctes
|
||||||
if(note < 0 || note > 10) {
|
if(note < 0 || note > 10) {
|
||||||
throw new Error("Note principale invalide");
|
throw new Error("Note principale invalide");
|
||||||
}
|
}
|
||||||
for(let nom in notesAutre) {
|
for(let nom in notesAutre) {
|
||||||
if(notesAutre[nom] < 0 || notesAutre[nom] > 10) {
|
if(notesAutre[nom] < 0 || notesAutre[nom] > 10) {
|
||||||
throw new Error("Note " + notesAutre[nom] +"/10 invalide");
|
throw new Error("Note " + notesAutre[nom] +"/10 invalide");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Classe qui représente l'auteur d'un avis
|
//Classe qui représente l'auteur d'un avis
|
||||||
export class Auteur {
|
export class Auteur {
|
||||||
/**
|
/**
|
||||||
* Constructeur
|
* Constructeur
|
||||||
* @param {Number} age L'age de l'auteur
|
* @param {Number} age L'age de l'auteur
|
||||||
* @param {String} sexe Le sexe de la personne (doit être dans l'array validSexes défini au début de ce fichier)
|
* @param {String} sexe Le sexe de la personne (doit être dans l'array validSexes défini au début de ce fichier)
|
||||||
*/
|
*/
|
||||||
constructor(age=null, sexe=null) {
|
constructor(age=null, sexe=null) {
|
||||||
this.age = age;
|
this.age = age;
|
||||||
this.sexe = sexe;
|
this.sexe = sexe;
|
||||||
|
|
||||||
//Verification des données
|
//Verification des données
|
||||||
if(sexe != undefined && !validSexes.includes(sexe) ) {
|
if(sexe != undefined && !validSexes.includes(sexe) ) {
|
||||||
throw new Error("Sexe invalide");
|
throw new Error("Sexe invalide");
|
||||||
}
|
}
|
||||||
if(age != undefined && age <= 0) {
|
if(age != undefined && age <= 0) {
|
||||||
throw new Error("Age invalide");
|
throw new Error("Age invalide");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import * as dotenv from 'dotenv'
|
import * as dotenv from 'dotenv'
|
||||||
import mysql from 'mysql2';
|
import mysql from 'mysql2';
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const conn = mysql.createConnection({
|
const conn = mysql.createConnection({
|
||||||
host: process.env.DB_HOST,
|
host: process.env.DB_HOST,
|
||||||
user: process.env.DB_USER,
|
user: process.env.DB_USER,
|
||||||
password: process.env.DB_PASSWORD,
|
password: process.env.DB_PASSWORD,
|
||||||
database: process.env.DB_NAME,
|
database: process.env.DB_NAME,
|
||||||
multipleStatements: true,
|
multipleStatements: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
conn.connect();
|
conn.connect();
|
||||||
|
|
||||||
export default conn;
|
export default conn;
|
||||||
@@ -1,28 +1,29 @@
|
|||||||
import * as dotenv from 'dotenv';
|
import * as dotenv from 'dotenv';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
import { addReviewFromRequest } from './borne/post_handler.js';
|
import { addReviewFromRequest } from './borne/post_handler.js';
|
||||||
import { addSocialReviewFromRequest } from './reseaux_sociaux/post_handler.js';
|
import { addSocialReviewFromRequest } from './reseaux_sociaux/post_handler.js';
|
||||||
import { startCronJobs } from './stats/update_stats.js';
|
import { startCronJobs } from './stats/update_stats.js';
|
||||||
import * as borneHandler from './borne/get_handler.js';
|
import * as borneHandler from './borne/get_handler.js';
|
||||||
|
import cors from "cors";
|
||||||
const app = express();
|
|
||||||
|
const app = express();
|
||||||
app.use(bodyParser.urlencoded({extended:true}))
|
app.use(bodyParser.urlencoded({extended:true}))
|
||||||
dotenv.config()
|
app.use(cors({origin:'*'}))
|
||||||
app.post('/add_review', (req,res) => addReviewFromRequest(req,res));
|
dotenv.config()
|
||||||
app.post('/add_social_review', (req,res) => addSocialReviewFromRequest(req,res));
|
app.post('/add_review', (req,res) => addReviewFromRequest(req,res));
|
||||||
|
app.post('/add_social_review', (req,res) => addSocialReviewFromRequest(req,res));
|
||||||
app.get('/borne/get_last_reviews', borneHandler.handleGetLastReviews);
|
|
||||||
app.get('/borne/get_review', borneHandler.handleGetReview);
|
app.get('/borne/get_last_reviews', borneHandler.handleGetLastReviews);
|
||||||
app.get('/borne/get_criteres', borneHandler.handleGetCriteres);
|
app.get('/borne/get_review', borneHandler.handleGetReview);
|
||||||
app.get('/borne/notes_autres', borneHandler.handleGetNotesAutres);
|
app.get('/borne/get_criteres', borneHandler.handleGetCriteres);
|
||||||
app.get('/borne/get_stats', borneHandler.handleGetStats);
|
app.get('/borne/notes_autres', borneHandler.handleGetNotesAutres);
|
||||||
|
app.get('/borne/get_stats', borneHandler.handleGetStats);
|
||||||
|
|
||||||
startCronJobs();
|
|
||||||
|
startCronJobs();
|
||||||
app.listen(process.env.PORT, () => {
|
|
||||||
console.log("Server démaré sur le port " + process.env.PORT)
|
app.listen(process.env.PORT, () => {
|
||||||
})
|
console.log("Server démaré sur le port " + process.env.PORT)
|
||||||
|
})
|
||||||
|
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "Telereview",
|
"author": "Telereview",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.20.1",
|
"body-parser": "^1.20.1",
|
||||||
"cron": "^2.1.0",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.0.3",
|
"cron": "^2.1.0",
|
||||||
"express": "^4.18.2",
|
"dotenv": "^16.0.3",
|
||||||
"mysql2": "^2.3.3"
|
"express": "^4.18.2",
|
||||||
}
|
"mysql2": "^2.3.3"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,85 +1,85 @@
|
|||||||
import { ReseauxAuteur, ReseauxReview } from './structures.js';
|
import { ReseauxAuteur, ReseauxReview } from './structures.js';
|
||||||
import conn from '../database.js';
|
import conn from '../database.js';
|
||||||
import {getSourceId} from '../utils.js';
|
import {getSourceId} from '../utils.js';
|
||||||
|
|
||||||
/**Récupérer l'id d'un auteur particulier dans la base de donnée s'il existe
|
/**Récupérer l'id d'un auteur particulier dans la base de donnée s'il existe
|
||||||
* @param {ReseauxAuteur} author L'auteur dont on veut l'id*
|
* @param {ReseauxAuteur} author L'auteur dont on veut l'id*
|
||||||
* @returns Une Promise qui donne l'id de l'utilisateur ou null si il n'existe pas
|
* @returns Une Promise qui donne l'id de l'utilisateur ou null si il n'existe pas
|
||||||
*/
|
*/
|
||||||
const getAuteurId = (author) => {
|
const getAuteurId = (author) => {
|
||||||
return new Promise(async (resolve,reject) => {
|
return new Promise(async (resolve,reject) => {
|
||||||
const sql = "SELECT id FROM reseaux_sociaux_auteurs WHERE nom_utilisateur = ? AND source_id = ? AND lien = ?;"
|
const sql = "SELECT id FROM reseaux_sociaux_auteurs WHERE nom_utilisateur = ? AND source_id = ? AND lien = ?;"
|
||||||
let sourceId = await getSourceId(author.source);
|
let sourceId = await getSourceId(author.source);
|
||||||
let query = conn.query(sql, [author.nom, sourceId, author.lien], (err, res) => {
|
let query = conn.query(sql, [author.nom, sourceId, author.lien], (err, res) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
reject(new Error(err.message))
|
reject(new Error(err.message))
|
||||||
}else {
|
}else {
|
||||||
if(res.length > 0) {
|
if(res.length > 0) {
|
||||||
resolve(res[0].id);
|
resolve(res[0].id);
|
||||||
}else {
|
}else {
|
||||||
resolve(null);
|
resolve(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ajoute un auteur de commentaire sur les réseaux sociaux a la bdd
|
* ajoute un auteur de commentaire sur les réseaux sociaux a la bdd
|
||||||
* @param {ReseauxAuteur} author
|
* @param {ReseauxAuteur} author
|
||||||
* @returns Une Promise qui donne l'id de l'utilisateur
|
* @returns Une Promise qui donne l'id de l'utilisateur
|
||||||
*/
|
*/
|
||||||
const addAuteur = (author, sourceId) => {
|
const addAuteur = (author, sourceId) => {
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
const sql = "INSERT INTO reseaux_sociaux_auteurs (nom_utilisateur, source_id, lien) VALUES (?);"
|
const sql = "INSERT INTO reseaux_sociaux_auteurs (nom_utilisateur, source_id, lien) VALUES (?);"
|
||||||
conn.query(sql, [[author.nom,sourceId, author.lien]], (err, res) => {
|
conn.query(sql, [[author.nom,sourceId, author.lien]], (err, res) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
reject(new Error(err.message))
|
reject(new Error(err.message))
|
||||||
}else {
|
}else {
|
||||||
resolve(res.insertId);
|
resolve(res.insertId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ajoute un avis dans la BDD
|
* Ajoute un avis dans la BDD
|
||||||
* @param {ReseauxReview} review L'avis a ajouter
|
* @param {ReseauxReview} review L'avis a ajouter
|
||||||
* @param {Number} authorId ID de l'auteur dans la bdd
|
* @param {Number} authorId ID de l'auteur dans la bdd
|
||||||
* @param {Number} sourceId ID Du réseau social source dans la bdd
|
* @param {Number} sourceId ID Du réseau social source dans la bdd
|
||||||
* @returns une Promise qui renvoie l'ID de la review
|
* @returns une Promise qui renvoie l'ID de la review
|
||||||
*/
|
*/
|
||||||
const addReview = (review, authorId, sourceId) => {
|
const addReview = (review, authorId, sourceId) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const sql = "INSERT INTO reseaux_sociaux_avis (source_id, note, commentaire, auteur_id, lien_source, date) VALUES (?);"
|
const sql = "INSERT INTO reseaux_sociaux_avis (source_id, note, commentaire, auteur_id, lien_source, date) VALUES (?);"
|
||||||
conn.query(sql, [[sourceId, review.note, review.commentaire, authorId, review.lien, review.date]], (err, res) => {
|
conn.query(sql, [[sourceId, review.note, review.commentaire, authorId, review.lien, review.date]], (err, res) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
reject(new Error(err.message))
|
reject(new Error(err.message))
|
||||||
}else {
|
}else {
|
||||||
resolve(res.insertId);
|
resolve(res.insertId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Traite une requête POST pour ajouter un avis récupéré sur les réseaux sociaux
|
* Traite une requête POST pour ajouter un avis récupéré sur les réseaux sociaux
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
* @param {*} res
|
* @param {*} res
|
||||||
*/
|
*/
|
||||||
export const addSocialReviewFromRequest = async (req,res) => {
|
export const addSocialReviewFromRequest = async (req,res) => {
|
||||||
try {
|
try {
|
||||||
const author = new ReseauxAuteur(req.body.auteur_nom, req.body.source, req.body.auteur_lien);
|
const author = new ReseauxAuteur(req.body.auteur_nom, req.body.source, req.body.auteur_lien);
|
||||||
const review = new ReseauxReview(author, req.body.source, req.body.date, req.body.note, req.body.commentaire, req.body.lien)
|
const review = new ReseauxReview(author, req.body.source, req.body.date, req.body.note, req.body.commentaire, req.body.lien)
|
||||||
let sourceId = await getSourceId(review.source);
|
let sourceId = await getSourceId(review.source);
|
||||||
let authorId = await getAuteurId(author);
|
let authorId = await getAuteurId(author);
|
||||||
if(authorId == null) {
|
if(authorId == null) {
|
||||||
authorId = await addAuteur(author, sourceId);
|
authorId = await addAuteur(author, sourceId);
|
||||||
}
|
}
|
||||||
await addReview(review, authorId, sourceId);
|
await addReview(review, authorId, sourceId);
|
||||||
res.send("success")
|
res.send("success")
|
||||||
}catch(err) {
|
}catch(err) {
|
||||||
res.status(500).send("Error : " + err.message)
|
res.status(500).send("Error : " + err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,39 +1,39 @@
|
|||||||
import { Review } from "../borne/structures.js";
|
import { Review } from "../borne/structures.js";
|
||||||
|
|
||||||
export class ReseauxReview extends Review{
|
export class ReseauxReview extends Review{
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {ReseauxAuteur} auteur Auteur de l'avis
|
* @param {ReseauxAuteur} auteur Auteur de l'avis
|
||||||
* @param {String} source La source de l'avis
|
* @param {String} source La source de l'avis
|
||||||
* @param {String} date La date de l'avis au format YYYY-MM-DD
|
* @param {String} date La date de l'avis au format YYYY-MM-DD
|
||||||
* @param {Number} note Nombre entre 0 et 10, la note attribuée
|
* @param {Number} note Nombre entre 0 et 10, la note attribuée
|
||||||
* @param {String} commentaire Le commentaire laissé par l'utilisateur
|
* @param {String} commentaire Le commentaire laissé par l'utilisateur
|
||||||
* @param {String} lien Lien vers le commentaire
|
* @param {String} lien Lien vers le commentaire
|
||||||
*/
|
*/
|
||||||
constructor(auteur, source, date, note=null, commentaire=null, lien=null,) {
|
constructor(auteur, source, date, note=null, commentaire=null, lien=null,) {
|
||||||
super(auteur,note,source,commentaire);
|
super(auteur,note,source,commentaire);
|
||||||
this.lien = lien;
|
this.lien = lien;
|
||||||
this.date = date;
|
this.date = date;
|
||||||
if((typeof lien !== "string" && lien != null)) {
|
if((typeof lien !== "string" && lien != null)) {
|
||||||
throw new Error("Lien invalide");
|
throw new Error("Lien invalide");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ReseauxAuteur {
|
export class ReseauxAuteur {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {String} nom Nom de l'utilisateur
|
* @param {String} nom Nom de l'utilisateur
|
||||||
* @param {String} source Réseau social de provenance de l'utilisateur
|
* @param {String} source Réseau social de provenance de l'utilisateur
|
||||||
* @param {String} lien Lien vers le profil de l'utilisateur
|
* @param {String} lien Lien vers le profil de l'utilisateur
|
||||||
*/
|
*/
|
||||||
constructor(nom, source, lien=null) {
|
constructor(nom, source, lien=null) {
|
||||||
this.nom = nom;
|
this.nom = nom;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.lien = lien;
|
this.lien = lien;
|
||||||
|
|
||||||
if((typeof this.nom !== "string") || (typeof this.source !== "string")){
|
if((typeof this.nom !== "string") || (typeof this.source !== "string")){
|
||||||
throw new Error("Auteur invalide");
|
throw new Error("Auteur invalide");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,50 +1,50 @@
|
|||||||
/*
|
/*
|
||||||
Dans cette requête il faut replacer DAY_COUNT_DELAY par le nombre de jours sur lequel calculer les stats, STATS_GENERAL_TABLE_NAME par la table dans laquelle mettre les stats globales (par exemple stats_general_jour si les valeurs sont calculées sur 1 jour) et pareil pour STATS_AUTRES_TABLE_NAME
|
Dans cette requête il faut replacer DAY_COUNT_DELAY par le nombre de jours sur lequel calculer les stats, STATS_GENERAL_TABLE_NAME par la table dans laquelle mettre les stats globales (par exemple stats_general_jour si les valeurs sont calculées sur 1 jour) et pareil pour STATS_AUTRES_TABLE_NAME
|
||||||
C'est un peu bizzare comme technique mais j'ai pas trouvé de meilleur solution
|
C'est un peu bizzare comme technique mais j'ai pas trouvé de meilleur solution
|
||||||
*/
|
*/
|
||||||
SET @date_limite = DATE_ADD(NOW(), INTERVAL -DAY_COUNT_DELAY DAY);
|
SET @date_limite = DATE_ADD(NOW(), INTERVAL -DAY_COUNT_DELAY DAY);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
On récupère les notes notes moyennes sur la periode, en séparant global, borne et site
|
On récupère les notes notes moyennes sur la periode, en séparant global, borne et site
|
||||||
*/
|
*/
|
||||||
|
|
||||||
SELECT @moyenne_globale:=AVG(note_principale)
|
SELECT @moyenne_globale:=AVG(note_principale)
|
||||||
FROM borne_avis
|
FROM borne_avis
|
||||||
WHERE borne_avis.date > @date_limite;
|
WHERE borne_avis.date > @date_limite;
|
||||||
|
|
||||||
SELECT @moyenne_borne:=AVG(note_principale)
|
SELECT @moyenne_borne:=AVG(note_principale)
|
||||||
FROM borne_avis
|
FROM borne_avis
|
||||||
JOIN sources ON sources.id = borne_avis.source_id
|
JOIN sources ON sources.id = borne_avis.source_id
|
||||||
WHERE borne_avis.date > @date_limite AND sources.nom = "borne";
|
WHERE borne_avis.date > @date_limite AND sources.nom = "borne";
|
||||||
|
|
||||||
SELECT @moyenne_site:=AVG(note_principale)
|
SELECT @moyenne_site:=AVG(note_principale)
|
||||||
FROM borne_avis
|
FROM borne_avis
|
||||||
JOIN sources ON sources.id = borne_avis.source_id
|
JOIN sources ON sources.id = borne_avis.source_id
|
||||||
WHERE borne_avis.date > @date_limite AND sources.nom = "website";
|
WHERE borne_avis.date > @date_limite AND sources.nom = "website";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
On récupère la distribution de sexes
|
On récupère la distribution de sexes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
SELECT @stats_f:=COUNT(*) FROM borne_avis
|
SELECT @stats_f:=COUNT(*) FROM borne_avis
|
||||||
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
||||||
WHERE sexe='f' AND date > @date_limite;
|
WHERE sexe='f' AND date > @date_limite;
|
||||||
SELECT @stats_h:=COUNT(*) FROM borne_avis
|
SELECT @stats_h:=COUNT(*) FROM borne_avis
|
||||||
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
||||||
WHERE sexe='h' AND date > @date_limite;
|
WHERE sexe='h' AND date > @date_limite;
|
||||||
SELECT @stats_a:=COUNT(*) FROM borne_avis
|
SELECT @stats_a:=COUNT(*) FROM borne_avis
|
||||||
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
JOIN borne_auteurs ON borne_avis.id_auteur = borne_auteurs.id
|
||||||
WHERE sexe='a' AND date > @date_limite;
|
WHERE sexe='a' AND date > @date_limite;
|
||||||
|
|
||||||
SET @dist_sexe = CONCAT(@stats_f,",",@stats_h,",",@stats_a);
|
SET @dist_sexe = CONCAT(@stats_f,",",@stats_h,",",@stats_a);
|
||||||
|
|
||||||
INSERT INTO STATS_GENERAL_TABLE_NAME (moyenne_globale, moyenne_borne, moyenne_site, dist_sexe) VALUES (@moyenne_globale, @moyenne_borne, @moyenne_site, @dist_sexe);
|
INSERT INTO STATS_GENERAL_TABLE_NAME (moyenne_globale, moyenne_borne, moyenne_site, dist_sexe) VALUES (@moyenne_globale, @moyenne_borne, @moyenne_site, @dist_sexe);
|
||||||
|
|
||||||
INSERT INTO STATS_AUTRES_TABLE_NAME (critere_id, note)
|
INSERT INTO STATS_AUTRES_TABLE_NAME (critere_id, note)
|
||||||
SELECT critere_id, AVG(note) as moyenne FROM borne_notes_autre
|
SELECT critere_id, AVG(note) as moyenne FROM borne_notes_autre
|
||||||
WHERE borne_notes_autre.date > @date_limite
|
WHERE borne_notes_autre.date > @date_limite
|
||||||
GROUP BY critere_id
|
GROUP BY critere_id
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO : Calcul de la distribution d'age
|
TODO : Calcul de la distribution d'age
|
||||||
*/
|
*/
|
||||||
@@ -1,69 +1,69 @@
|
|||||||
import conn from '../database.js';
|
import conn from '../database.js';
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { CronJob } from 'cron';
|
import { CronJob } from 'cron';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calcules les stats sur une periode donnée et les stocke dans la BDD
|
* Calcules les stats sur une periode donnée et les stocke dans la BDD
|
||||||
* @param {Number} timePeriod Periode de temps sur laquelle calculer les données. Par exemple 7 si on veut faire les stats des 7 derniers jours
|
* @param {Number} timePeriod Periode de temps sur laquelle calculer les données. Par exemple 7 si on veut faire les stats des 7 derniers jours
|
||||||
* @param {String} generalTableName Nom de la table dans laquelle mettre les statistiques générales (tables valides : stats_general_jour stats_general_semaine stats_general_mois stats_general_annee)
|
* @param {String} generalTableName Nom de la table dans laquelle mettre les statistiques générales (tables valides : stats_general_jour stats_general_semaine stats_general_mois stats_general_annee)
|
||||||
* @param {*} otherTableName Nom de la table dans laquelle mettre les statistiques spécifiques (tables valides : stats_autres_jour stats_autres_autres_autres_mois stats_general_annee)
|
* @param {*} otherTableName Nom de la table dans laquelle mettre les statistiques spécifiques (tables valides : stats_autres_jour stats_autres_autres_autres_mois stats_general_annee)
|
||||||
* @returns Une Promise qui résout si la requête a fonctionnée
|
* @returns Une Promise qui résout si la requête a fonctionnée
|
||||||
*/
|
*/
|
||||||
const computeStats = async (timePeriod, generalTableName, otherTableName) => {
|
const computeStats = async (timePeriod, generalTableName, otherTableName) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let sql = fs.readFileSync("stats/update_request.sql").toString();
|
let sql = fs.readFileSync("stats/update_request.sql").toString();
|
||||||
sql = sql
|
sql = sql
|
||||||
.replace(new RegExp("DAY_COUNT_DELAY",'g'), timePeriod.toString())
|
.replace(new RegExp("DAY_COUNT_DELAY",'g'), timePeriod.toString())
|
||||||
.replace(new RegExp("STATS_GENERAL_TABLE_NAME", 'g'), generalTableName)
|
.replace(new RegExp("STATS_GENERAL_TABLE_NAME", 'g'), generalTableName)
|
||||||
.replace(new RegExp('STATS_AUTRES_TABLE_NAME', 'g'), otherTableName)
|
.replace(new RegExp('STATS_AUTRES_TABLE_NAME', 'g'), otherTableName)
|
||||||
conn.query(sql, (err, res) => {
|
conn.query(sql, (err, res) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
}else {
|
}else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export const startCronJobs = () => {
|
export const startCronJobs = () => {
|
||||||
//Update les stats journalières tout les jours a minuit
|
//Update les stats journalières tout les jours a minuit
|
||||||
new CronJob(
|
new CronJob(
|
||||||
'0 0 * * * * ',
|
'0 0 * * * * ',
|
||||||
() => {
|
() => {
|
||||||
computeStats(1,"stats_general_jour","stats_autres_jour");
|
computeStats(1,"stats_general_jour","stats_autres_jour");
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
//update les stats de la semaine tous les lundis a minuit
|
//update les stats de la semaine tous les lundis a minuit
|
||||||
new CronJob(
|
new CronJob(
|
||||||
'0 0 * * 1 * ',
|
'0 0 * * 1 * ',
|
||||||
() => {
|
() => {
|
||||||
computeStats(7,"stats_general_semaine","stats_autres_jour");
|
computeStats(7,"stats_general_semaine","stats_autres_jour");
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
//update les stats mensuelles les 1er du mois a minuit
|
//update les stats mensuelles les 1er du mois a minuit
|
||||||
new CronJob(
|
new CronJob(
|
||||||
'0 0 1 * * * ',
|
'0 0 1 * * * ',
|
||||||
() => {
|
() => {
|
||||||
computeStats(30, "stats_general_mois","stats_autres_mois");
|
computeStats(30, "stats_general_mois","stats_autres_mois");
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
//update les stats annuelles les premire de l'an a minuit
|
//update les stats annuelles les premire de l'an a minuit
|
||||||
new CronJob(
|
new CronJob(
|
||||||
'0 0 1 1 * * ',
|
'0 0 1 1 * * ',
|
||||||
() => {
|
() => {
|
||||||
computeStats(365, "stats_general_annee","stats_autres_annee");
|
computeStats(365, "stats_general_annee","stats_autres_annee");
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
console.log("All cronjobs initiated")
|
console.log("All cronjobs initiated")
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import conn from './database.js';
|
import conn from './database.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renvoie l'ID dans la BDD d'une source de donnée
|
* Renvoie l'ID dans la BDD d'une source de donnée
|
||||||
* @param {String} source la source dont on veut récup l'id
|
* @param {String} source la source dont on veut récup l'id
|
||||||
* @returns une promise qui renvoie l'id de la source
|
* @returns une promise qui renvoie l'id de la source
|
||||||
*/
|
*/
|
||||||
export const getSourceId = (source) => {
|
export const getSourceId = (source) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const sql = "SELECT id from sources WHERE nom = ?";
|
const sql = "SELECT id from sources WHERE nom = ?";
|
||||||
conn.query(sql, [source], (err, res) => {
|
conn.query(sql, [source], (err, res) => {
|
||||||
if(res.length == 0) {
|
if(res.length == 0) {
|
||||||
reject(new Error("Invalid source"))
|
reject(new Error("Invalid source"))
|
||||||
}else {
|
}else {
|
||||||
resolve(res[0].id);
|
resolve(res[0].id);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
2
code/setup.sh
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
sudo modprobe v4l2loopback devices=2
|
||||||
3
code/video_loopback/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
FROM alpine:latest
|
||||||
|
RUN apk add --no-cache ffmpeg
|
||||||
|
CMD ["ffmpeg","-video_size","640x480","-f","video4linux2","-i","/dev/video0","-codec","copy","-f","v4l2","/dev/video1","-codec","copy","-f","v4l2","/dev/video2", "-loglevel","warning"]
|
||||||
12
docs/dupliquer_camera.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Methode pour accéder au flux video a la fois depuis firefox et depuis opencv
|
||||||
|
* installer v4l2loopback :
|
||||||
|
* Télécharger : `git clone https://github.com/umlaeute/v4l2loopback.git`
|
||||||
|
* Installer avec : `make` puis `sudo make install`
|
||||||
|
* activer le module : `sudo modprobe v4l2loopback devices=2`
|
||||||
|
* Erreur possible : opperation not permitted : il faut désactiver secure boot
|
||||||
|
* OU `apt update && apt install v4l2loopback-dkms v4l2loopback-utils`
|
||||||
|
* [Innutile si container video_loopback present] Faire looper la camera /dev/video0 sur les autres
|
||||||
|
* installer ffmpeg : `sudo apt get install ffmpeg`
|
||||||
|
* activer le loopback : `ffmpeg -video_size 640x480 -f video4linux2 -i /dev/video0 -codec copy -f v4l2 /dev/video1 -codec copy -f v4l2 /dev/video2`
|
||||||
|
|
||||||
|
Maintenant on peut par exemple utiliser /dev/video2 sur firefox et /dev/video13sur opencv sans que cela ne pose de problème
|
||||||
16
docs/liste_pages_web.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
Liste des pages web à concevoir dans tout le projet
|
||||||
|
* Interface de la borne
|
||||||
|
* Interface avec retour vidéo et effets quand un gest est détécté
|
||||||
|
* page de remerciment, avec indication que la personne peut parler pour développer son avis
|
||||||
|
* Page de remerciment
|
||||||
|
|
||||||
|
* Formulaire de retour d'avis
|
||||||
|
* Formulaire
|
||||||
|
* Page de remerciement
|
||||||
|
|
||||||
|
* Interface admin
|
||||||
|
* Page principale avec les stats principales
|
||||||
|
* Page liste des avis récents
|
||||||
|
* page stats détaillées sur la borne ou on peut changer l'intervalle de calcul des stats
|
||||||
|
* Page avec les derniers avis récup sur les réseaux sociaux
|
||||||
|
* Page avec les stats récup sur les réseaux sociaux
|
||||||
6
docs/rapports_pan2/module_hardware/TODO.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Truc a corriger sur le rapport
|
||||||
|
* XRemplacer les screen par des vrai graphiques
|
||||||
|
* XAjouter des standard deviation aux graphiques
|
||||||
|
* XPeut être ajouter des valeurs absolues sur les benchmark
|
||||||
|
* X Regarder si utiliser un power saving mode sur le CPU est intéressant (température vs perofrmances)
|
||||||
|
* X Dire que c'est pas du temps mais que ça nous convient
|
||||||
|
After Width: | Height: | Size: 1.3 MiB |
5
docs/rapports_pan2/module_hardware/benchmark/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
FROM alpine:latest
|
||||||
|
RUN apk add --no-cache sysbench
|
||||||
|
WORKDIR /app
|
||||||
|
COPY benchmark_script.sh /app/benchmark_script.sh
|
||||||
|
CMD ["sh","benchmark_script.sh"]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
sysbench --test=cpu run >>sysbench.log
|
||||||
|
sysbench --test=memory run >>sysbench.log
|
||||||
|
sysbench --test=fileio --file-test-mode=rndrw prepare
|
||||||
|
sysbench --test=fileio --file-test-mode=rndrw run >>sysbench.log
|
||||||
|
sysbench --test=fileio cleanup
|
||||||
114
docs/rapports_pan2/module_hardware/benchmark/sysbench.txt
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
sysbench 1.0.20 (using system LuaJIT 2.1.0-beta3)
|
||||||
|
|
||||||
|
Running the test with following options:
|
||||||
|
Number of threads: 1
|
||||||
|
Initializing random number generator from current time
|
||||||
|
|
||||||
|
|
||||||
|
Prime numbers limit: 10000
|
||||||
|
|
||||||
|
Initializing worker threads...
|
||||||
|
|
||||||
|
Threads started!
|
||||||
|
|
||||||
|
CPU speed:
|
||||||
|
events per second: 613.74
|
||||||
|
|
||||||
|
General statistics:
|
||||||
|
total time: 10.0013s
|
||||||
|
total number of events: 6140
|
||||||
|
|
||||||
|
Latency (ms):
|
||||||
|
min: 1.62
|
||||||
|
avg: 1.63
|
||||||
|
max: 2.10
|
||||||
|
95th percentile: 1.64
|
||||||
|
sum: 9998.37
|
||||||
|
|
||||||
|
Threads fairness:
|
||||||
|
events (avg/stddev): 6140.0000/0.00
|
||||||
|
execution time (avg/stddev): 9.9984/0.00
|
||||||
|
|
||||||
|
sysbench 1.0.20 (using system LuaJIT 2.1.0-beta3)
|
||||||
|
|
||||||
|
Running the test with following options:
|
||||||
|
Number of threads: 1
|
||||||
|
Initializing random number generator from current time
|
||||||
|
|
||||||
|
|
||||||
|
Running memory speed test with the following options:
|
||||||
|
block size: 1KiB
|
||||||
|
total size: 102400MiB
|
||||||
|
operation: write
|
||||||
|
scope: global
|
||||||
|
|
||||||
|
Initializing worker threads...
|
||||||
|
|
||||||
|
Threads started!
|
||||||
|
|
||||||
|
Total operations: 33390517 (3338076.27 per second)
|
||||||
|
|
||||||
|
32607.93 MiB transferred (3259.84 MiB/sec)
|
||||||
|
|
||||||
|
|
||||||
|
General statistics:
|
||||||
|
total time: 10.0000s
|
||||||
|
total number of events: 33390517
|
||||||
|
|
||||||
|
Latency (ms):
|
||||||
|
min: 0.00
|
||||||
|
avg: 0.00
|
||||||
|
max: 0.10
|
||||||
|
95th percentile: 0.00
|
||||||
|
sum: 4949.30
|
||||||
|
|
||||||
|
Threads fairness:
|
||||||
|
events (avg/stddev): 33390517.0000/0.00
|
||||||
|
execution time (avg/stddev): 4.9493/0.00
|
||||||
|
|
||||||
|
sysbench 1.0.20 (using system LuaJIT 2.1.0-beta3)
|
||||||
|
|
||||||
|
Running the test with following options:
|
||||||
|
Number of threads: 1
|
||||||
|
Initializing random number generator from current time
|
||||||
|
|
||||||
|
|
||||||
|
Extra file open flags: (none)
|
||||||
|
128 files, 16MiB each
|
||||||
|
2GiB total file size
|
||||||
|
Block size 16KiB
|
||||||
|
Number of IO requests: 0
|
||||||
|
Read/Write ratio for combined random IO test: 1.50
|
||||||
|
Periodic FSYNC enabled, calling fsync() each 100 requests.
|
||||||
|
Calling fsync() at the end of test, Enabled.
|
||||||
|
Using synchronous I/O mode
|
||||||
|
Doing random r/w test
|
||||||
|
Initializing worker threads...
|
||||||
|
|
||||||
|
Threads started!
|
||||||
|
|
||||||
|
|
||||||
|
File operations:
|
||||||
|
reads/s: 789.46
|
||||||
|
writes/s: 526.31
|
||||||
|
fsyncs/s: 1691.85
|
||||||
|
|
||||||
|
Throughput:
|
||||||
|
read, MiB/s: 12.34
|
||||||
|
written, MiB/s: 8.22
|
||||||
|
|
||||||
|
General statistics:
|
||||||
|
total time: 10.0292s
|
||||||
|
total number of events: 30045
|
||||||
|
|
||||||
|
Latency (ms):
|
||||||
|
min: 0.00
|
||||||
|
avg: 0.33
|
||||||
|
max: 17.49
|
||||||
|
95th percentile: 2.48
|
||||||
|
sum: 9966.50
|
||||||
|
|
||||||
|
Threads fairness:
|
||||||
|
events (avg/stddev): 30045.0000/0.00
|
||||||
|
execution time (avg/stddev): 9.9665/0.00
|
||||||
|
|
||||||
114
docs/rapports_pan2/module_hardware/benchmark/sysbench_docker.txt
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
sysbench 1.0.20 (using bundled LuaJIT 2.1.0-beta2)
|
||||||
|
|
||||||
|
Running the test with following options:
|
||||||
|
Number of threads: 1
|
||||||
|
Initializing random number generator from current time
|
||||||
|
|
||||||
|
|
||||||
|
Prime numbers limit: 10000
|
||||||
|
|
||||||
|
Initializing worker threads...
|
||||||
|
|
||||||
|
Threads started!
|
||||||
|
|
||||||
|
CPU speed:
|
||||||
|
events per second: 552.76
|
||||||
|
|
||||||
|
General statistics:
|
||||||
|
total time: 10.0015s
|
||||||
|
total number of events: 5530
|
||||||
|
|
||||||
|
Latency (ms):
|
||||||
|
min: 1.79
|
||||||
|
avg: 1.81
|
||||||
|
max: 2.34
|
||||||
|
95th percentile: 1.82
|
||||||
|
sum: 9999.03
|
||||||
|
|
||||||
|
Threads fairness:
|
||||||
|
events (avg/stddev): 5530.0000/0.00
|
||||||
|
execution time (avg/stddev): 9.9990/0.00
|
||||||
|
|
||||||
|
sysbench 1.0.20 (using bundled LuaJIT 2.1.0-beta2)
|
||||||
|
|
||||||
|
Running the test with following options:
|
||||||
|
Number of threads: 1
|
||||||
|
Initializing random number generator from current time
|
||||||
|
|
||||||
|
|
||||||
|
Running memory speed test with the following options:
|
||||||
|
block size: 1KiB
|
||||||
|
total size: 102400MiB
|
||||||
|
operation: write
|
||||||
|
scope: global
|
||||||
|
|
||||||
|
Initializing worker threads...
|
||||||
|
|
||||||
|
Threads started!
|
||||||
|
|
||||||
|
Total operations: 28620880 (2861268.15 per second)
|
||||||
|
|
||||||
|
27950.08 MiB transferred (2794.21 MiB/sec)
|
||||||
|
|
||||||
|
|
||||||
|
General statistics:
|
||||||
|
total time: 10.0001s
|
||||||
|
total number of events: 28620880
|
||||||
|
|
||||||
|
Latency (ms):
|
||||||
|
min: 0.00
|
||||||
|
avg: 0.00
|
||||||
|
max: 0.10
|
||||||
|
95th percentile: 0.00
|
||||||
|
sum: 5501.03
|
||||||
|
|
||||||
|
Threads fairness:
|
||||||
|
events (avg/stddev): 28620880.0000/0.00
|
||||||
|
execution time (avg/stddev): 5.5010/0.00
|
||||||
|
|
||||||
|
sysbench 1.0.20 (using bundled LuaJIT 2.1.0-beta2)
|
||||||
|
|
||||||
|
Running the test with following options:
|
||||||
|
Number of threads: 1
|
||||||
|
Initializing random number generator from current time
|
||||||
|
|
||||||
|
|
||||||
|
Extra file open flags: (none)
|
||||||
|
128 files, 16MiB each
|
||||||
|
2GiB total file size
|
||||||
|
Block size 16KiB
|
||||||
|
Number of IO requests: 0
|
||||||
|
Read/Write ratio for combined random IO test: 1.50
|
||||||
|
Periodic FSYNC enabled, calling fsync() each 100 requests.
|
||||||
|
Calling fsync() at the end of test, Enabled.
|
||||||
|
Using synchronous I/O mode
|
||||||
|
Doing random r/w test
|
||||||
|
Initializing worker threads...
|
||||||
|
|
||||||
|
Threads started!
|
||||||
|
|
||||||
|
|
||||||
|
File operations:
|
||||||
|
reads/s: 785.13
|
||||||
|
writes/s: 523.42
|
||||||
|
fsyncs/s: 1687.54
|
||||||
|
|
||||||
|
Throughput:
|
||||||
|
read, MiB/s: 12.27
|
||||||
|
written, MiB/s: 8.18
|
||||||
|
|
||||||
|
General statistics:
|
||||||
|
total time: 10.0083s
|
||||||
|
total number of events: 29866
|
||||||
|
|
||||||
|
Latency (ms):
|
||||||
|
min: 0.00
|
||||||
|
avg: 0.33
|
||||||
|
max: 18.46
|
||||||
|
95th percentile: 2.48
|
||||||
|
sum: 9963.06
|
||||||
|
|
||||||
|
Threads fairness:
|
||||||
|
events (avg/stddev): 29866.0000/0.00
|
||||||
|
execution time (avg/stddev): 9.9631/0.00
|
||||||
|
|
||||||
8
docs/rapports_pan2/module_hardware/dockerps.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||||
|
4ff5ebc88a1f code-backend_reconaissance "python main.py" 3 hours ago Up 2 hours 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp backend_reconaissance
|
||||||
|
02e8569d1926 code-video_loopback "ffmpeg -video_size …" 3 hours ago Up 2 hours video_loopback
|
||||||
|
d50295f99ae5 phpmyadmin:latest "/docker-entrypoint.…" 4 hours ago Up 2 hours 0.0.0.0:8000->80/tcp, :::8000->80/tcp phpmyadmin
|
||||||
|
a5efc4ddae1b code-reviews_api "docker-entrypoint.s…" 4 hours ago Up 2 hours 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp reviews_api
|
||||||
|
830786df4f5f httpd:latest "httpd-foreground" 4 hours ago Up 2 hours 0.0.0.0:8888->80/tcp, :::8888->80/tcp interface_borne
|
||||||
|
2fd04a8fe768 mysql:latest "docker-entrypoint.s…" 4 hours ago Up 2 hours (healthy) 3306/tcp, 33060/tcp db
|
||||||
|
9999c72bb59f httpd:latest "httpd-foreground" 4 hours ago Up 2 hours 0.0.0.0:80->80/tcp, :::80->80/tcp interface_admin
|
||||||
BIN
docs/rapports_pan2/module_hardware/img/benchmark.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/rapports_pan2/module_hardware/img/formats_camera.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/rapports_pan2/module_hardware/img/htop.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/rapports_pan2/module_hardware/img/latence.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/rapports_pan2/module_hardware/img/reconnaissance_fps.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
111
docs/rapports_pan2/module_hardware/rapport.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Rapport PAN2
|
||||||
|
|
||||||
|
## Choix du materiel
|
||||||
|
* **Ecran** (InnoLux N133HSE): Pour l'écran nous avons choisi un écran 13.3" (non tactile), nous avons estimé la taille suffisante pour un confort de lecture à distance raisonable de la borne. De plus cet écran présente un système de montage à vis qui facilitera son intégration dans la borne.
|
||||||
|
|
||||||
|
* **Camera** : Nous avions le choix entre 2 camera
|
||||||
|
* Une webcam Logitech c525
|
||||||
|
* Grand choix de résolutions avec un framerate élevé par exemple 640x480@30fps, 960x720@30fps, 1920x1080@5fps
|
||||||
|
* Mais champs de vision beaucoup trop faible pour notre application
|
||||||
|
* Une camera de surveillance grand angle de la marque ELP
|
||||||
|
* Choix de résoltions restreint, mais utilisable dans des bonnes conditions de luminosité
|
||||||
|
|
||||||
|
<img src="img/formats_camera.png" height=250>
|
||||||
|
|
||||||
|
* Champs de vision adapté a notre utilisation
|
||||||
|
* Cette camera apporte des distortions importantes sur les bords, cela compléxifie la reconnaissance d'image.
|
||||||
|
* Nous avons choisi la deuxième camera pour les raisons citées ci-dessus
|
||||||
|
|
||||||
|
* **Ordinateur** :
|
||||||
|
Nous avons à notre disposition un AMD Cubi doté de 4Go de RAM, 128Go de SSD et un Intel Core i3 5005U. L'objectif du reste de ce document est d'évaluer si les performances de cet ordinateur sont suffisantes pour notre application.
|
||||||
|
|
||||||
|
## Choix du système d'exploitation
|
||||||
|
Nous avons choisi d'installer debian 11, une distribution de linux légère avec laquelle nous étions familiers. Pour l'environement de bureau nous avons choisi LXDE là aussi pour le minimalisme qu'il présente au vu des performances de la machine. Nous avons par la suite désinstallé tout les packets installé par défaut par LXDE dont nous n'avions pas besoin. (Par exemple les jeux, la calculatrice, etc.)
|
||||||
|
|
||||||
|
## Benchmarking
|
||||||
|
Pour évaluer la capacité du materiel à supporter la charge de notre application, nous avons executé en parallèle les applications qui seront utilisés pour le produit final ou une application aux besoins équivalents quand cela n'était pas possible. De ce fait ce benchmark n'est pas exact mais nous donnera un ordre d'idée sur les besoins de notre projet.
|
||||||
|
|
||||||
|
### Liste des modules à executer
|
||||||
|
|
||||||
|
Pour ce faire nous avons mis en place un container Docker par module de notre application :
|
||||||
|
* **db** : un serveur mysql basique pour la base de donnée
|
||||||
|
* **phpmyadmin** : l'interface phpmyadmin pour gérer la base de donnée. Ce container n'est pas absolument nécessaire au produit final mais permet d'administrer facilement la base de donnée
|
||||||
|
* **review_api** : un serveur express pour servir l'api permettant de récupérer et ajouter des avis dans la base de donnée, ainsi que de calculer les statistiques.
|
||||||
|
* **interface_borne**: un serveur apache2 permettant de servir l'interface graphique de la borne.
|
||||||
|
* **interface_admin**: idem pour l'interface graphique, ce serveur pourait être fusioné avec
|
||||||
|
**interface_borne** mais dans le cadre de ce benchmarking on les gardera séparés.
|
||||||
|
* **backend_reconnaissance**: Ce container s'occupera de la reconnaissance audio et video de la borne. Cependant ces deux processus ne seront jamais actifs en même temps, de plus la reconnaissance d'image sera la plus couteuse en terme de puissance de calcul. C'est pour cela qu'ici nous avons uniquement utilisé mediapipe hands (bibliothèque python de reconaissance de mains) avec une implémentation de la communication avec l'interface de la borne comme programme équivalent.
|
||||||
|
* **video_loopback** : ce container sert à contourner un problème que nous avons rencontré avec la gestion des camera par linux. En effet seul un programme peut accéder au flux d'une camera à la fois. C'est pour cela que nous avons utilisé `v4l2loopback` avec `ffmpeg` pour dupliquer le flux de notre camera dans 2 cameras virtuelles.
|
||||||
|
|
||||||
|
En parallèle firefox est ouvert pour afficher l'interface graphique de la borne.
|
||||||
|
### Résultats
|
||||||
|
En réglant la camera a 640x480@30fps, aucune perte d'image n'est observée dans le retour vidéo dans firefox.
|
||||||
|
|
||||||
|
Nous avons observé la capacité de Mediapipe l'image de la camera et de la communiquer à l'interface web qui affiche le résultat.
|
||||||
|
Nous avons obtenu les résultats suivants pour 2 modes de gestion de fréquence du processeur (la gestions automatique de base schedutil et performance qui utillise la fréquence maximale)
|
||||||
|
| CPU Scheduler | FPS moyens | Ecart type |
|
||||||
|
|--------------|------------|------------|
|
||||||
|
| schedutil |10.4 | 1.4 |
|
||||||
|
| performance | 10.1 | 0.9 |
|
||||||
|
|
||||||
|
|
||||||
|
Ce taux de rafraichissement est suffisant pour que l'application paraisse relativement réactive à l'utilisateur, bien que cela ne soit pas du temps réel.
|
||||||
|
|
||||||
|
Pendant ce temps l'api de traitement des avis et la base de donnée fonctionnent correctement en affichant une lattence de 8ms pour une récupération de la liste d'avis.
|
||||||
|
|
||||||
|
Pendance ce temps l'utilisation du processeur qui varie de 250% à 280% (sur 400% pour les 4 coeurs) et une utilisation de la RAM de 50% (1.96Go) ce qui nous laisse de la marge en cas d'ajout imprévu.
|
||||||
|
|
||||||
|
Les processus utilisant le plus de CPU sont la reconaissance d'image (70%) et firefox pour afficher l'interface de la borne (70-80%). En cas de besoin ces valeurs pourront être diminuées au prix de la fluidité du retour vidéo.
|
||||||
|
Pour la RAM c'est le serveur mysql (10%) et firefox (10%) qui consomment le plus.
|
||||||
|
|
||||||
|
Pour ce qui est de la température, comme la borne sera dans un environement fermé, il était important de tester le bon fonctionnement du materiel dans ces conditions. Nous avons laissé tourner l'application pendant 2h dans une boite en carton fermée. Au début du test la température du processeur était de 50°C, au bout de 2h la température était montée a 70°C, ce qui reste assez faible pour ne pas limiter les performances du CPU.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sensors
|
||||||
|
acpitz-acpi-0
|
||||||
|
Adapter: ACPI interface
|
||||||
|
temp1: +27.8°C (crit = +110.0°C)
|
||||||
|
temp2: +29.8°C (crit = +110.0°C)
|
||||||
|
|
||||||
|
coretemp-isa-0000
|
||||||
|
Adapter: ISA adapter
|
||||||
|
Package id 0: +69.0°C (high = +105.0°C, crit = +105.0°C)
|
||||||
|
Core 0: +69.0°C (high = +105.0°C, crit = +105.0°C)
|
||||||
|
Core 1: +68.0°C (high = +105.0°C, crit = +105.0°C)
|
||||||
|
```
|
||||||
|
<div style="page-break-after: always;"></div>
|
||||||
|
|
||||||
|
## Impact de l'utilisation de Docker
|
||||||
|
Nous nous sommes également posé la question de l'impact de l'utilisation de docker dans les performances de notre projet. Pour mesurer cela, nous avons effectué des benchmark directement sur le système puis dans un container Docker pour mesurer la différence. Nous avons utlisé sysbench pour évaluer les performances du CPU, de la RAM et du disque (écriture/lecture aléatoire).
|
||||||
|
|
||||||
|
Le script permettant de faire le benchmark
|
||||||
|
```sh
|
||||||
|
sysbench --test=cpu run >>sysbench.log
|
||||||
|
sysbench --test=memory run >>sysbench.log
|
||||||
|
sysbench --test=fileio --file-test-mode=rndrw prepare
|
||||||
|
sysbench --test=fileio --file-test-mode=rndrw run >>sysbench.log
|
||||||
|
sysbench --test=fileio cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
Le Dockerfile du container dans lequel nous avons exectué le même script
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
FROM alpine:latest
|
||||||
|
RUN apk add --no-cache sysbench
|
||||||
|
WORKDIR /app
|
||||||
|
COPY benchmark_script.sh /app/benchmark_script.sh
|
||||||
|
CMD ["sh","benchmark_script.sh"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Les résultats de ce test on permis de conclure que l'impact de docker était négligeable.
|
||||||
|
|
||||||
|
<img src="img/benchmark.png" width=400>
|
||||||
|
|
||||||
|
| Type | CPU (Evts/s) | RAM (Mbi/s) | Disque lecture(Mbi/s) | Disque écriture (Mbi/s) |
|
||||||
|
|--------------|------------|------------|---|---|
|
||||||
|
| Normal | 613.14 | 3259.84 | 12.34 | 8.22 |
|
||||||
|
| Docker | 552.76 | 2794.21 |12.27 | 8.18 |
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Au vu des tests effectués le materiel dont nous disposons semble adapté à notre projet. Il serait cependant possible de réduire l'utilisation faite de l'ordinateur embarqué dans la borne en déplaçant la partie stockage et traitement des avis sur un autre serveur.
|
||||||
BIN
docs/rapports_pan2/module_hardware/rapport.pdf
Normal file
11
docs/rapports_pan2/module_hardware/sensors.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
acpitz-acpi-0
|
||||||
|
Adapter: ACPI interface
|
||||||
|
temp1: +27.8°C (crit = +110.0°C)
|
||||||
|
temp2: +29.8°C (crit = +110.0°C)
|
||||||
|
|
||||||
|
coretemp-isa-0000
|
||||||
|
Adapter: ISA adapter
|
||||||
|
Package id 0: +69.0°C (high = +105.0°C, crit = +105.0°C)
|
||||||
|
Core 0: +69.0°C (high = +105.0°C, crit = +105.0°C)
|
||||||
|
Core 1: +68.0°C (high = +105.0°C, crit = +105.0°C)
|
||||||
|
|
||||||
@@ -1,127 +1,127 @@
|
|||||||
= Rapport d’avancement: groupe 7.1
|
= Rapport d’avancement: groupe 7.1
|
||||||
PACT groupe 7.1 (Téléreview)
|
PACT groupe 7.1 (Téléreview)
|
||||||
ifdef::env-gitlab,env-browser[:outfilesuffix: .adoc]
|
ifdef::env-gitlab,env-browser[:outfilesuffix: .adoc]
|
||||||
:doctype: book
|
:doctype: book
|
||||||
:stem: latexmath
|
:stem: latexmath
|
||||||
:source-highlighter: coderay
|
:source-highlighter: coderay
|
||||||
:toc-title: Table des matières
|
:toc-title: Table des matières
|
||||||
:toc: macro
|
:toc: macro
|
||||||
:sectnums:
|
:sectnums:
|
||||||
:imagesdir: images
|
:imagesdir: images
|
||||||
|
|
||||||
|
|
||||||
// Partie non numérotée
|
// Partie non numérotée
|
||||||
:sectnums!:
|
:sectnums!:
|
||||||
== « Téléreview »
|
== « Téléreview »
|
||||||
|
|
||||||
=== Membres du groupe
|
=== Membres du groupe
|
||||||
|
|
||||||
* DAS Keshav
|
* DAS Keshav
|
||||||
* BELHORMA Wissal
|
* BELHORMA Wissal
|
||||||
* GRIVAUX Yannis
|
* GRIVAUX Yannis
|
||||||
* HAMDAOUI Yasmine
|
* HAMDAOUI Yasmine
|
||||||
* KOUACHE Lounes
|
* KOUACHE Lounes
|
||||||
* ROUSSEL Quentin
|
* ROUSSEL Quentin
|
||||||
* SEMPÉRÉ Lucas
|
* SEMPÉRÉ Lucas
|
||||||
* MEZIANE Sara
|
* MEZIANE Sara
|
||||||
|
|
||||||
=== Tuteur
|
=== Tuteur
|
||||||
|
|
||||||
* Enzo Tartaglione
|
* Enzo Tartaglione
|
||||||
|
|
||||||
=== Encadrant génie logiciel
|
=== Encadrant génie logiciel
|
||||||
|
|
||||||
* Marc Hulcelle
|
* Marc Hulcelle
|
||||||
|
|
||||||
<<<
|
<<<
|
||||||
|
|
||||||
== Résumé du sujet choisi en français (PAN1)
|
== Résumé du sujet choisi en français (PAN1)
|
||||||
|
|
||||||
Le but de notre projet c’est de créer une borne interactive qui permet de récupérer les avis d’utilisateurs accompagné d’une interface qui donne au gérant des statistiques sur les avis de ces usagers.
|
Le but de notre projet c’est de créer une borne interactive qui permet de récupérer les avis d’utilisateurs accompagné d’une interface qui donne au gérant des statistiques sur les avis de ces usagers.
|
||||||
La borne marche par reconnaissance d’image et de voix et comprend donc un micro et une caméra en plus d’un écran. La borne, placée à la sortie d’un lieu accueillant du public, renvoie aux visiteurs une image et les invite à donner leur avis par un signe de la main ou une expression faciale. Ceci permet déjà d’avoir un plus large panel d'émotions et d’avis qu’avec les boutons qu’on pouvait trouver en magasin avant la crise Covid. De plus, elle permet aux utilisateurs de laisser un commentaire construit en parlant à la borne qui ensuite traite le commentaire et en déduit les mots-clés et les sujets importants puis les confronte avec ce qui est dit sur les réseaux sociaux en faisant un scrapping de réseaux sociaux. Toutes ces informations sont traitées puis synthétisées et le gérant peut ainsi avoir accès à un compte-rendu simple et clair.
|
La borne marche par reconnaissance d’image et de voix et comprend donc un micro et une caméra en plus d’un écran. La borne, placée à la sortie d’un lieu accueillant du public, renvoie aux visiteurs une image et les invite à donner leur avis par un signe de la main ou une expression faciale. Ceci permet déjà d’avoir un plus large panel d'émotions et d’avis qu’avec les boutons qu’on pouvait trouver en magasin avant la crise Covid. De plus, elle permet aux utilisateurs de laisser un commentaire construit en parlant à la borne qui ensuite traite le commentaire et en déduit les mots-clés et les sujets importants puis les confronte avec ce qui est dit sur les réseaux sociaux en faisant un scrapping de réseaux sociaux. Toutes ces informations sont traitées puis synthétisées et le gérant peut ainsi avoir accès à un compte-rendu simple et clair.
|
||||||
|
|
||||||
|
|
||||||
== English Summary (PAN1)
|
== English Summary (PAN1)
|
||||||
|
|
||||||
|
|
||||||
The goal of our project is to create an interactive terminal that allows us to retrieve users' opinions accompanied by an interface that gives the manager statistics on what was collected.
|
The goal of our project is to create an interactive terminal that allows us to retrieve users' opinions accompanied by an interface that gives the manager statistics on what was collected.
|
||||||
The terminal works by image and voice recognition and therefore includes a microphone and a camera in addition to a screen. The terminal, placed at the exit of a place open to the public, invites visitors to give their opinion by a wave of the hand or a facial expression. This already makes it possible to have a wider range of emotions and opinions than with the buttons that could be found in stores before the Covid crisis. In addition, it allows users to leave a comment by talking to the terminal, which then processes the comment and deduces the keywords and important subjects from it, then compares them with what is said on social networks . All this information is processed and then summarized and the manager can thus have access to a simple and clear report.
|
The terminal works by image and voice recognition and therefore includes a microphone and a camera in addition to a screen. The terminal, placed at the exit of a place open to the public, invites visitors to give their opinion by a wave of the hand or a facial expression. This already makes it possible to have a wider range of emotions and opinions than with the buttons that could be found in stores before the Covid crisis. In addition, it allows users to leave a comment by talking to the terminal, which then processes the comment and deduces the keywords and important subjects from it, then compares them with what is said on social networks . All this information is processed and then summarized and the manager can thus have access to a simple and clear report.
|
||||||
|
|
||||||
|
|
||||||
<<<
|
<<<
|
||||||
////
|
////
|
||||||
*Notes concenant le rapport*
|
*Notes concenant le rapport*
|
||||||
|
|
||||||
Les différentes pages du document sont rédigées en utilisant le langage
|
Les différentes pages du document sont rédigées en utilisant le langage
|
||||||
AsciiDoc. Le squelette de rapport contient des exemples avec entre autres:
|
AsciiDoc. Le squelette de rapport contient des exemples avec entre autres:
|
||||||
|
|
||||||
* des images,
|
* des images,
|
||||||
* des liens,
|
* des liens,
|
||||||
* des équations.
|
* des équations.
|
||||||
|
|
||||||
La structure du rapport (parties, sections et la relation avec les
|
La structure du rapport (parties, sections et la relation avec les
|
||||||
différents fichiers) se trouve dans le fichier courant.
|
différents fichiers) se trouve dans le fichier courant.
|
||||||
**Prenez le temps** de supprimer le texte de remplissage et les sections non
|
**Prenez le temps** de supprimer le texte de remplissage et les sections non
|
||||||
utilisées pour l'instant. Vous pouvez par exemple commenter ces parties non
|
utilisées pour l'instant. Vous pouvez par exemple commenter ces parties non
|
||||||
utilisées pour qu'elles n'apparaissent pas dans le document final. En Asciidoc,
|
utilisées pour qu'elles n'apparaissent pas dans le document final. En Asciidoc,
|
||||||
il suffit de les précéder de deux slashs (`//`).
|
il suffit de les précéder de deux slashs (`//`).
|
||||||
////
|
////
|
||||||
|
|
||||||
<<<
|
<<<
|
||||||
toc::[]
|
toc::[]
|
||||||
<<<
|
<<<
|
||||||
|
|
||||||
// On numérote le reste des sections
|
// On numérote le reste des sections
|
||||||
:sectnums:
|
:sectnums:
|
||||||
|
|
||||||
== Étude d’antériorité et justification de la proposition (PAN1)
|
== Étude d’antériorité et justification de la proposition (PAN1)
|
||||||
|
|
||||||
include::proposition/proposition.adoc[Description de la proposition]
|
include::proposition/proposition.adoc[Description de la proposition]
|
||||||
|
|
||||||
include::proposition/etat-de-l-art.adoc[Description de l’état de l’art]
|
include::proposition/etat-de-l-art.adoc[Description de l’état de l’art]
|
||||||
|
|
||||||
<<<
|
<<<
|
||||||
|
|
||||||
== Scénarios d’usage (PAN1)
|
== Scénarios d’usage (PAN1)
|
||||||
|
|
||||||
include::scenario/scenario.adoc[Scénarios d’usage]
|
include::scenario/scenario.adoc[Scénarios d’usage]
|
||||||
|
|
||||||
<<<
|
<<<
|
||||||
|
|
||||||
== Architecture du projet (PAN1)
|
== Architecture du projet (PAN1)
|
||||||
|
|
||||||
include::architecture/schema.adoc[Schéma d’architecture]
|
include::architecture/schema.adoc[Schéma d’architecture]
|
||||||
|
|
||||||
include::architecture/interfaces.adoc[Description des interfaces]
|
include::architecture/interfaces.adoc[Description des interfaces]
|
||||||
|
|
||||||
include::architecture/sequence.adoc[Diagramme de séquence]
|
include::architecture/sequence.adoc[Diagramme de séquence]
|
||||||
|
|
||||||
include::architecture/ihm.adoc[Interface utilisateur graphique]
|
include::architecture/ihm.adoc[Interface utilisateur graphique]
|
||||||
|
|
||||||
<<<
|
<<<
|
||||||
|
|
||||||
== Organisation du projet (PAN1)
|
== Organisation du projet (PAN1)
|
||||||
|
|
||||||
include::organisation/planification.adoc[Diagramme de planification temporel des tâches]
|
include::organisation/planification.adoc[Diagramme de planification temporel des tâches]
|
||||||
|
|
||||||
//include::organisation/plan-tests.adoc[Plans de test (PAN2+)]
|
//include::organisation/plan-tests.adoc[Plans de test (PAN2+)]
|
||||||
|
|
||||||
////
|
////
|
||||||
<<<
|
<<<
|
||||||
|
|
||||||
[bibliography]
|
[bibliography]
|
||||||
== Bibliographie (PAN1+)
|
== Bibliographie (PAN1+)
|
||||||
|
|
||||||
include::References.adoc[Bibliographie]
|
include::References.adoc[Bibliographie]
|
||||||
|
|
||||||
<<<
|
<<<
|
||||||
== Annexes
|
== Annexes
|
||||||
|
|
||||||
include::annexes/modifications.adoc[Modifications (PAN2+)]
|
include::annexes/modifications.adoc[Modifications (PAN2+)]
|
||||||
|
|
||||||
include::annexes/avancement.adoc[Avancement des modules]
|
include::annexes/avancement.adoc[Avancement des modules]
|
||||||
|
|
||||||
include::annexes/moduleX.adoc[Avancement module X]
|
include::annexes/moduleX.adoc[Avancement module X]
|
||||||
|
|
||||||
include::annexes/moduleY.adoc[Avancement module Y]
|
include::annexes/moduleY.adoc[Avancement module Y]
|
||||||
////
|
////
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
|
|
||||||
*_Note : Liste des références exploitées. Une référence complète
|
*_Note : Liste des références exploitées. Une référence complète
|
||||||
donne titre, auteur(s), date, journal, revue, source de publication,
|
donne titre, auteur(s), date, journal, revue, source de publication,
|
||||||
titre de conférence, numéro, pages. Une webographie est aussi
|
titre de conférence, numéro, pages. Une webographie est aussi
|
||||||
envisageable : titre, auteur, date, page web_*
|
envisageable : titre, auteur, date, page web_*
|
||||||
|
|
||||||
* [[RefShannon]]RefShannon : *C. E. SHANNON*, _A Mathematical Theory
|
* [[RefShannon]]RefShannon : *C. E. SHANNON*, _A Mathematical Theory
|
||||||
of Communication_, Reprinted with corrections from The Bell System
|
of Communication_, Reprinted with corrections from The Bell System
|
||||||
Technical Journal, pages 379–423, 623–656, Vol. 27, 1948,
|
Technical Journal, pages 379–423, 623–656, Vol. 27, 1948,
|
||||||
http://sites.google.com/site/parthochoudhury/aMToC_CShannon.pdf
|
http://sites.google.com/site/parthochoudhury/aMToC_CShannon.pdf
|
||||||
* [[VitrinePACT]]VitrinePACT : _Vitrine des projets PACT_,
|
* [[VitrinePACT]]VitrinePACT : _Vitrine des projets PACT_,
|
||||||
https://pact.wp.mines-telecom.fr/vitrine/
|
https://pact.wp.mines-telecom.fr/vitrine/
|
||||||
* [[TOTO]]XXX : *M. S. Otor*, _Best paper ever_, Livre de la jungle,
|
* [[TOTO]]XXX : *M. S. Otor*, _Best paper ever_, Livre de la jungle,
|
||||||
Volume 2, pages 33-34, 1777
|
Volume 2, pages 33-34, 1777
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
=== Suivis des modules (Après PAN1)
|
=== Suivis des modules (Après PAN1)
|
||||||
|
|
||||||
Insérez ici votre biblio et vos avancées techniques par module (réunions
|
Insérez ici votre biblio et vos avancées techniques par module (réunions
|
||||||
experts, pseudo-code d’algorithmes, description détaillée de vos réalisations, etc.).
|
experts, pseudo-code d’algorithmes, description détaillée de vos réalisations, etc.).
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
puts("hello world!");
|
puts("hello world!");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,47 @@
|
|||||||
=== Modifications (PAN2+)
|
=== Modifications (PAN2+)
|
||||||
|
|
||||||
==== Modifications de fond
|
==== Modifications de fond
|
||||||
|
|
||||||
Tableau des modifications de fond apportées au projet avec validation
|
Tableau des modifications de fond apportées au projet avec validation
|
||||||
des experts et encadrant informatique
|
des experts et encadrant informatique
|
||||||
|
|
||||||
[cols=",,",options="header",]
|
[cols=",,",options="header",]
|
||||||
|====
|
|====
|
||||||
| libellé / date | Description brève | Validé par :
|
| libellé / date | Description brève | Validé par :
|
||||||
| | |
|
| | |
|
||||||
| | |
|
| | |
|
||||||
|====
|
|====
|
||||||
|
|
||||||
==== Modifications du rapport
|
==== Modifications du rapport
|
||||||
|
|
||||||
Vous noterez dans cette section les modifications apportées au rapport
|
Vous noterez dans cette section les modifications apportées au rapport
|
||||||
depuis le PAN précédent. Si votre planification temporelle a été
|
depuis le PAN précédent. Si votre planification temporelle a été
|
||||||
modifiée, vous laisserez l’ancienne planification dans cette annexe.
|
modifiée, vous laisserez l’ancienne planification dans cette annexe.
|
||||||
|
|
||||||
===== Modifications du rapport au PAN2
|
===== Modifications du rapport au PAN2
|
||||||
|
|
||||||
Fusce ac nisi. Integer volutpat elementum metus. Vivamus luctus
|
Fusce ac nisi. Integer volutpat elementum metus. Vivamus luctus
|
||||||
ultricies diam. Curabitur euismod. Vivamus quam. Nunc ante. Nulla mi
|
ultricies diam. Curabitur euismod. Vivamus quam. Nunc ante. Nulla mi
|
||||||
nulla, vehicula nec, ultrices a, tincidunt vel, enim.
|
nulla, vehicula nec, ultrices a, tincidunt vel, enim.
|
||||||
|
|
||||||
Suspendisse potenti. Aenean sed velit. Nunc a urna quis turpis imperdiet
|
Suspendisse potenti. Aenean sed velit. Nunc a urna quis turpis imperdiet
|
||||||
sollicitudin. Mauris aliquam mauris ut tortor. Pellentesque tincidunt
|
sollicitudin. Mauris aliquam mauris ut tortor. Pellentesque tincidunt
|
||||||
mattis nibh. In id lectus eu.
|
mattis nibh. In id lectus eu.
|
||||||
|
|
||||||
===== Modifications du rapport au PAN3
|
===== Modifications du rapport au PAN3
|
||||||
|
|
||||||
Dolor aliquam elit, a commodo nisi felis nec nibh. Nulla facilisi. Etiam
|
Dolor aliquam elit, a commodo nisi felis nec nibh. Nulla facilisi. Etiam
|
||||||
at tortor. Vivamus quis sapien nec magna scelerisque lobortis.
|
at tortor. Vivamus quis sapien nec magna scelerisque lobortis.
|
||||||
|
|
||||||
Curabitur tincidunt viverra justo. Cum sociis natoque penatibus et
|
Curabitur tincidunt viverra justo. Cum sociis natoque penatibus et
|
||||||
magnis dis parturient montes, nascetur ridiculus mus. Sed eros ante,
|
magnis dis parturient montes, nascetur ridiculus mus. Sed eros ante,
|
||||||
mattis ullamcorper, posuere quis, tempor vel, metus. Maecenas cursus
|
mattis ullamcorper, posuere quis, tempor vel, metus. Maecenas cursus
|
||||||
cursus lacus. Sed.
|
cursus lacus. Sed.
|
||||||
|
|
||||||
===== Modifications du rapport au PAN4
|
===== Modifications du rapport au PAN4
|
||||||
|
|
||||||
Hac habitasse platea dictumst. Cras quis lacus. Vestibulum rhoncus
|
Hac habitasse platea dictumst. Cras quis lacus. Vestibulum rhoncus
|
||||||
congue lacus. Vivamus euismod, felis quis commodo viverra, dolor elit
|
congue lacus. Vivamus euismod, felis quis commodo viverra, dolor elit
|
||||||
dictum ante, et mollis eros augue at est. Class aptent taciti sociosqu
|
dictum ante, et mollis eros augue at est. Class aptent taciti sociosqu
|
||||||
ad litora torquent per conubia nostra, per inceptos himenaeos. Nulla
|
ad litora torquent per conubia nostra, per inceptos himenaeos. Nulla
|
||||||
lectus sem, tristique sed, semper in, hendrerit non, sem. Vivamus.
|
lectus sem, tristique sed, semper in, hendrerit non, sem. Vivamus.
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
=== Module X
|
=== Module X
|
||||||
|
|
||||||
==== Descriptions
|
==== Descriptions
|
||||||
|
|
||||||
[source,cpp]
|
[source,cpp]
|
||||||
----
|
----
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
class M
|
class M
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void hello()
|
void hello()
|
||||||
{
|
{
|
||||||
cout << "hello" << endl;
|
cout << "hello" << endl;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
M m;
|
M m;
|
||||||
m.hello();
|
m.hello();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
include::code/test.c[]
|
include::code/test.c[]
|
||||||
----
|
----
|
||||||
|
|
||||||
Et l'équation:
|
Et l'équation:
|
||||||
|
|
||||||
[latexmath]
|
[latexmath]
|
||||||
++++
|
++++
|
||||||
A \in \mathcal{M}_{u,k}(\mathbb{R}) et B \in \mathcal{M}_{k,f}(\mathbb{R})
|
A \in \mathcal{M}_{u,k}(\mathbb{R}) et B \in \mathcal{M}_{k,f}(\mathbb{R})
|
||||||
++++
|
++++
|
||||||
|
|
||||||
==== Avancement
|
==== Avancement
|
||||||
|
|
||||||
==== Bibliographie spécifique
|
==== Bibliographie spécifique
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
=== Module Y
|
=== Module Y
|
||||||
|
|
||||||
==== Descriptions
|
==== Descriptions
|
||||||
|
|
||||||
==== Avancement
|
==== Avancement
|
||||||
|
|
||||||
==== Bibliographie spécifique
|
==== Bibliographie spécifique
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
=== Comptes Rendus de réunions
|
=== Comptes Rendus de réunions
|
||||||
|
|
||||||
Insérez ici vos comptes rendus de réunions : date, durée, participants,
|
Insérez ici vos comptes rendus de réunions : date, durée, participants,
|
||||||
sujets abordés.
|
sujets abordés.
|
||||||
|
|
||||||
==== Réunion du dd/mm/yyyy
|
==== Réunion du dd/mm/yyyy
|
||||||
|
|
||||||
* Présents:
|
* Présents:
|
||||||
** AA
|
** AA
|
||||||
** BB
|
** BB
|
||||||
** CC
|
** CC
|
||||||
* Sujets abordés
|
* Sujets abordés
|
||||||
** *xxx* : Nec, iaculis vel, mi. Nullam et augue vitae nunc tristique
|
** *xxx* : Nec, iaculis vel, mi. Nullam et augue vitae nunc tristique
|
||||||
vehicula. Suspendisse eget elit. Duis adipiscing dui non quam.
|
vehicula. Suspendisse eget elit. Duis adipiscing dui non quam.
|
||||||
* Actions à entreprendre:
|
* Actions à entreprendre:
|
||||||
** Fusce sollicitudin molestie dui. Sed magna orci, accumsan nec,
|
** Fusce sollicitudin molestie dui. Sed magna orci, accumsan nec,
|
||||||
viverra non, pharetra id, dui. Lorem ipsum dolor sit amet, consectetuer
|
viverra non, pharetra id, dui. Lorem ipsum dolor sit amet, consectetuer
|
||||||
adipiscing elit.
|
adipiscing elit.
|
||||||
** Interdum arcu, at pellentesque diam metus ut nulla. Vestibulum eu
|
** Interdum arcu, at pellentesque diam metus ut nulla. Vestibulum eu
|
||||||
dolor sit amet lacus varius fermentum. Morbi dolor enim, pulvinar eget.
|
dolor sit amet lacus varius fermentum. Morbi dolor enim, pulvinar eget.
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
=== Interface utilisateur graphique
|
=== Interface utilisateur graphique
|
||||||
|
|
||||||
|
|
||||||
.Interface graphique client
|
.Interface graphique client
|
||||||
image::../images/Interface_PACT-1.png[Interface_PACT-1]
|
image::../images/Interface_PACT-1.png[Interface_PACT-1]
|
||||||
|
|
||||||
.Interface graphique admin
|
.Interface graphique admin
|
||||||
image::../images/Interface_PACT-2.png[Interface_PACT-2]
|
image::../images/Interface_PACT-2.png[Interface_PACT-2]
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
=== Description des interfaces (PAN1 / PAN2)
|
=== Description des interfaces (PAN1 / PAN2)
|
||||||
|
|
||||||
//Pour le PAN1, il faut identifier et décrire sommairement toutes les
|
//Pour le PAN1, il faut identifier et décrire sommairement toutes les
|
||||||
//interfaces entre modules.
|
//interfaces entre modules.
|
||||||
|
|
||||||
//Pour le PAN2, il faut une description complète des interfaces.
|
//Pour le PAN2, il faut une description complète des interfaces.
|
||||||
|
|
||||||
SE et Image : le système transmet les données captées par la caméra embarquée au module d’image pour le traitement de celles-ci.
|
SE et Image : le système transmet les données captées par la caméra embarquée au module d’image pour le traitement de celles-ci.
|
||||||
|
|
||||||
SE et reconnaissance vocale : le système transmet le signal capté par le micro embarqué au module de reconnaissance vocale qui s’occupe de transformer ce signal en une chaine de caractère.
|
SE et reconnaissance vocale : le système transmet le signal capté par le micro embarqué au module de reconnaissance vocale qui s’occupe de transformer ce signal en une chaine de caractère.
|
||||||
|
|
||||||
Reconnaissance vocale et TAL : le module de reconnaissance vocal envoie la chaine de caractères qu’il a collectée au module de TAL pour l’analyse du texte.
|
Reconnaissance vocale et TAL : le module de reconnaissance vocal envoie la chaine de caractères qu’il a collectée au module de TAL pour l’analyse du texte.
|
||||||
|
|
||||||
TAL et base de donnée : Dans ces deux cas, après analyse des données anonymisées, les modules TAL et image envoient les résultats à la base de donnée qui va les conserver.
|
TAL et base de donnée : Dans ces deux cas, après analyse des données anonymisées, les modules TAL et image envoient les résultats à la base de donnée qui va les conserver.
|
||||||
|
|
||||||
Base de donnée / Analyse des données : Le module d'analyse des données récupère les données brutes dans la base de données et calcules des statistiques sur celles-ci.
|
Base de donnée / Analyse des données : Le module d'analyse des données récupère les données brutes dans la base de données et calcules des statistiques sur celles-ci.
|
||||||
|
|
||||||
Interface web / Analyse de donnée : L'interface web récupère les données analysées a l'aide d'une API pour les afficher.
|
Interface web / Analyse de donnée : L'interface web récupère les données analysées a l'aide d'une API pour les afficher.
|
||||||
|
|
||||||
Design et SE/ design et interface web : Le module design doit évoluer avec le module SE et interface web dans la conception des modules. Il se complète avec le module SE pour donner la borne physique. Et il se complète avec le module d’interface web pour donner l’outil utilisable par le client.
|
Design et SE/ design et interface web : Le module design doit évoluer avec le module SE et interface web dans la conception des modules. Il se complète avec le module SE pour donner la borne physique. Et il se complète avec le module d’interface web pour donner l’outil utilisable par le client.
|
||||||
|
|
||||||
|
|
||||||
//Il faut ici une description textuelle de chaque interface, c'est-à-dire chaque
|
//Il faut ici une description textuelle de chaque interface, c'est-à-dire chaque
|
||||||
//échange entre deux blocs.
|
//échange entre deux blocs.
|
||||||
//Si c’est une interface entre deux blocs informatiques, c’est une interface
|
//Si c’est une interface entre deux blocs informatiques, c’est une interface
|
||||||
//Java.
|
//Java.
|
||||||
//S’il y a des échanges de données complexes, il faut en décrire le format avec
|
//S’il y a des échanges de données complexes, il faut en décrire le format avec
|
||||||
//précision.
|
//précision.
|
||||||
//Si c’est une interface entre deux blocs électroniques, c’est une description
|
//Si c’est une interface entre deux blocs électroniques, c’est une description
|
||||||
//des signaux électroniques ou protocoles utilisés.
|
//des signaux électroniques ou protocoles utilisés.
|
||||||
|
|
||||||
//==== InterfaceBloc1-2
|
//==== InterfaceBloc1-2
|
||||||
|
|
||||||
//Description textuelle d’interface
|
//Description textuelle d’interface
|
||||||
|
|
||||||
//==== InterfaceBloc2-4
|
//==== InterfaceBloc2-4
|
||||||
|
|
||||||
//Description textuelle d’interface
|
//Description textuelle d’interface
|
||||||
|
|||||||
@@ -1,84 +1,84 @@
|
|||||||
=== Schéma d’architecture
|
=== Schéma d’architecture
|
||||||
|
|
||||||
[blockdiag]
|
[blockdiag]
|
||||||
....
|
....
|
||||||
blockdiag{
|
blockdiag{
|
||||||
default_fontsize=16
|
default_fontsize=16
|
||||||
default_group_color = "#7777FF"; node_height=85
|
default_group_color = "#7777FF"; node_height=85
|
||||||
node_width=130
|
node_width=130
|
||||||
reseaux_sociaux[label="Réseaux \nsociaux" , color="#D05E3F", shape = "flowchart.input"]
|
reseaux_sociaux[label="Réseaux \nsociaux" , color="#D05E3F", shape = "flowchart.input"]
|
||||||
interface_avis[label="Interface \nretours \nd'avis", color="#D05E3F",shape = "flowchart.input"]
|
interface_avis[label="Interface \nretours \nd'avis", color="#D05E3F",shape = "flowchart.input"]
|
||||||
group{
|
group{
|
||||||
reseaux_sociaux;interface_avis;
|
reseaux_sociaux;interface_avis;
|
||||||
}
|
}
|
||||||
micro[label="Micro", color="#D07115",shape = "flowchart.input"]
|
micro[label="Micro", color="#D07115",shape = "flowchart.input"]
|
||||||
cam[label="Caméra", color="#D07115",shape = "flowchart.input"]
|
cam[label="Caméra", color="#D07115",shape = "flowchart.input"]
|
||||||
group{
|
group{
|
||||||
cam;micro;
|
cam;micro;
|
||||||
}
|
}
|
||||||
traitement_audio[label="Traitement du \nsignal audio", color="#83EBDC"]
|
traitement_audio[label="Traitement du \nsignal audio", color="#83EBDC"]
|
||||||
ia_vocal[label="Reconnaissance \nvocale", color="#B3D04B"]
|
ia_vocal[label="Reconnaissance \nvocale", color="#B3D04B"]
|
||||||
ia_image[label="Reconnaissance \nd'image", color="#B3D04B"]
|
ia_image[label="Reconnaissance \nd'image", color="#B3D04B"]
|
||||||
traitement_language[label="Traitement \ndu language", color="#F787C8"]
|
traitement_language[label="Traitement \ndu language", color="#F787C8"]
|
||||||
droit[label="Droit", color="#CC0C30"]
|
droit[label="Droit", color="#CC0C30"]
|
||||||
bdd[label="Base de donnée", color="#FDFF85",shape = "flowchart.database"]
|
bdd[label="Base de donnée", color="#FDFF85",shape = "flowchart.database"]
|
||||||
analyse[label="Analyse des \ndonnées", color="#F787C8"]
|
analyse[label="Analyse des \ndonnées", color="#F787C8"]
|
||||||
interface_admin[label="Interface \nadministrateur", color="#D05E3F"]
|
interface_admin[label="Interface \nadministrateur", color="#D05E3F"]
|
||||||
|
|
||||||
reseaux_sociaux -> traitement_language[label='Avis textuel', fontsize=10];
|
reseaux_sociaux -> traitement_language[label='Avis textuel', fontsize=10];
|
||||||
interface_avis -> traitement_language [label='Commentaire', fontsize=10];
|
interface_avis -> traitement_language [label='Commentaire', fontsize=10];
|
||||||
droit -> bdd;
|
droit -> bdd;
|
||||||
micro -> traitement_audio [label='Son', fontsize=10] ;
|
micro -> traitement_audio [label='Son', fontsize=10] ;
|
||||||
traitement_audio -> ia_vocal [label='Son', fontsize=10];
|
traitement_audio -> ia_vocal [label='Son', fontsize=10];
|
||||||
ia_vocal -> traitement_language;
|
ia_vocal -> traitement_language;
|
||||||
traitement_language -> bdd [label='Avis', fontsize=10, folded];
|
traitement_language -> bdd [label='Avis', fontsize=10, folded];
|
||||||
bdd -> analyse ;
|
bdd -> analyse ;
|
||||||
analyse -> interface_admin [label='Données traitées', fontsize=10];
|
analyse -> interface_admin [label='Données traitées', fontsize=10];
|
||||||
cam -> ia_image;
|
cam -> ia_image;
|
||||||
ia_image -> bdd [label='Données interprétées', fontsize=10];
|
ia_image -> bdd [label='Données interprétées', fontsize=10];
|
||||||
droit -> bdd [label='Régulation des données', fontsize=10];
|
droit -> bdd [label='Régulation des données', fontsize=10];
|
||||||
|
|
||||||
}
|
}
|
||||||
....
|
....
|
||||||
|
|
||||||
==== Description des blocs
|
==== Description des blocs
|
||||||
|
|
||||||
===== Droit
|
===== Droit
|
||||||
|
|
||||||
Vérifier la conformité du projet, connaitre l'ensemble des restrictions auxquels est soumis notre projet afin de mieux se conformer à la loi sur le traitement des données personnelles.
|
Vérifier la conformité du projet, connaitre l'ensemble des restrictions auxquels est soumis notre projet afin de mieux se conformer à la loi sur le traitement des données personnelles.
|
||||||
|
|
||||||
===== Système embarqué
|
===== Système embarqué
|
||||||
|
|
||||||
Choix du hardware, fabrication de la borne, installation des logiciels et mise en production des autres modules.
|
Choix du hardware, fabrication de la borne, installation des logiciels et mise en production des autres modules.
|
||||||
|
|
||||||
===== Traitement du signal
|
===== Traitement du signal
|
||||||
|
|
||||||
Préparer le signal audio sortant du microphone pour le convertir en un signal analysable par le module reconnaissance vocale.
|
Préparer le signal audio sortant du microphone pour le convertir en un signal analysable par le module reconnaissance vocale.
|
||||||
|
|
||||||
===== Reconnaissance d'image
|
===== Reconnaissance d'image
|
||||||
|
|
||||||
A l'aide des images récupérées par la caméra et de réseaux neuronaux réussir à reconnaitre des gestes, des émotions sur un visage, l'âge et le sexe de la personne.
|
A l'aide des images récupérées par la caméra et de réseaux neuronaux réussir à reconnaitre des gestes, des émotions sur un visage, l'âge et le sexe de la personne.
|
||||||
|
|
||||||
===== Reconnaissance vocale
|
===== Reconnaissance vocale
|
||||||
|
|
||||||
Récupere le signal audio traité afin d'intentifier des ou les mots prononcés par l'utilisateur.
|
Récupere le signal audio traité afin d'intentifier des ou les mots prononcés par l'utilisateur.
|
||||||
|
|
||||||
===== Traitement du language
|
===== Traitement du language
|
||||||
|
|
||||||
A partir du texte reçus l'objectif est de catégoriser les avis suivant s'il sont positifs ou négatifs. De manière plus générale ce module extrait des informations des avis qui pourront par la suite êtres analysées plus facilement.
|
A partir du texte reçus l'objectif est de catégoriser les avis suivant s'il sont positifs ou négatifs. De manière plus générale ce module extrait des informations des avis qui pourront par la suite êtres analysées plus facilement.
|
||||||
|
|
||||||
===== Base de donnée
|
===== Base de donnée
|
||||||
|
|
||||||
La fonction de ce bloc est de récupérées et stockées les données du traitement du language et de la reconnaissance d'image qui auront été anonymisées.
|
La fonction de ce bloc est de récupérées et stockées les données du traitement du language et de la reconnaissance d'image qui auront été anonymisées.
|
||||||
|
|
||||||
===== Analyse des données
|
===== Analyse des données
|
||||||
|
|
||||||
Traiter, analyser et interpréter les données afin de générer les métriques importantes pour l'administrateur.
|
Traiter, analyser et interpréter les données afin de générer les métriques importantes pour l'administrateur.
|
||||||
|
|
||||||
===== Interface administrateur
|
===== Interface administrateur
|
||||||
|
|
||||||
Présente à l'administrateur de manière élégante, claire, organisées et détaillé les résultats des analyses faites sur les différentes avis.
|
Présente à l'administrateur de manière élégante, claire, organisées et détaillé les résultats des analyses faites sur les différentes avis.
|
||||||
|
|
||||||
===== Récupération de données de sources autres
|
===== Récupération de données de sources autres
|
||||||
|
|
||||||
L'objectif de ce bloc est de récupérer des données textuelles autres que les données recueillies par le bloc reconnaissance vocale pour enrichir la base d'avis utilisés par le bloc d'analyse de données. Ces sources autres peuvent être les commentaires laissés sur les réseaux sociaux ou d'une interface permettant de donner son avis à l'aide d'un formulaire.
|
L'objectif de ce bloc est de récupérer des données textuelles autres que les données recueillies par le bloc reconnaissance vocale pour enrichir la base d'avis utilisés par le bloc d'analyse de données. Ces sources autres peuvent être les commentaires laissés sur les réseaux sociaux ou d'une interface permettant de donner son avis à l'aide d'un formulaire.
|
||||||
|
|||||||
@@ -1,51 +1,51 @@
|
|||||||
=== Diagramme de séquence (optionnel)
|
=== Diagramme de séquence (optionnel)
|
||||||
|
|
||||||
==== Interaction entre l'utilisateur et la borne (Interaction gestuelle / vocale)
|
==== Interaction entre l'utilisateur et la borne (Interaction gestuelle / vocale)
|
||||||
[plantuml, sequence-x]
|
[plantuml, sequence-x]
|
||||||
....
|
....
|
||||||
actor utilisateur #99FF99
|
actor utilisateur #99FF99
|
||||||
boundary frontend as "Interface utilisateur" #4da6ff
|
boundary frontend as "Interface utilisateur" #4da6ff
|
||||||
boundary Camera
|
boundary Camera
|
||||||
boundary Micro
|
boundary Micro
|
||||||
participant "Back end\nmachine learning" as backend #ff8680
|
participant "Back end\nmachine learning" as backend #ff8680
|
||||||
database database as "Base de donnée"
|
database database as "Base de donnée"
|
||||||
|
|
||||||
utilisateur --> Camera : signe (main, visage)
|
utilisateur --> Camera : signe (main, visage)
|
||||||
Camera -> backend : flux vidéo
|
Camera -> backend : flux vidéo
|
||||||
backend -> database : signe identifié
|
backend -> database : signe identifié
|
||||||
backend -> frontend : signe identifié
|
backend -> frontend : signe identifié
|
||||||
frontend --> utilisateur: indicateur de prise \nen compte de l'avis
|
frontend --> utilisateur: indicateur de prise \nen compte de l'avis
|
||||||
utilisateur --> Micro : avis vocal
|
utilisateur --> Micro : avis vocal
|
||||||
Micro -> backend : flux audio
|
Micro -> backend : flux audio
|
||||||
backend -> database : mots clé ou \nphrases identifiés
|
backend -> database : mots clé ou \nphrases identifiés
|
||||||
backend -> frontend : phrase identifié
|
backend -> frontend : phrase identifié
|
||||||
frontend --> utilisateur: indicateur de prise \nen compte de l'avis
|
frontend --> utilisateur: indicateur de prise \nen compte de l'avis
|
||||||
....
|
....
|
||||||
|
|
||||||
==== Interaction entre l'utilisateur et la borne (QR Code)
|
==== Interaction entre l'utilisateur et la borne (QR Code)
|
||||||
[plantuml, sequence-x]
|
[plantuml, sequence-x]
|
||||||
....
|
....
|
||||||
actor utilisateur #99FF99
|
actor utilisateur #99FF99
|
||||||
participant qrcode as "QR Code"
|
participant qrcode as "QR Code"
|
||||||
boundary frontend as "Interface web" #4da6ff
|
boundary frontend as "Interface web" #4da6ff
|
||||||
participant "Back end" as backend #ff8680
|
participant "Back end" as backend #ff8680
|
||||||
database database as "Base de donnée"
|
database database as "Base de donnée"
|
||||||
|
|
||||||
utilisateur --> qrcode : flash
|
utilisateur --> qrcode : flash
|
||||||
utilisateur --> frontend : retour utlisateur\n(sous forme textuelle)
|
utilisateur --> frontend : retour utlisateur\n(sous forme textuelle)
|
||||||
frontend -> backend : retour utlisateur
|
frontend -> backend : retour utlisateur
|
||||||
backend -> database : retour utlisateur
|
backend -> database : retour utlisateur
|
||||||
....
|
....
|
||||||
|
|
||||||
==== Séquence d'analyse et affichange des données
|
==== Séquence d'analyse et affichange des données
|
||||||
[plantuml, sequence-x]
|
[plantuml, sequence-x]
|
||||||
....
|
....
|
||||||
actor utilisateur #99FF99
|
actor utilisateur #99FF99
|
||||||
boundary frontend as "Interface \nadministrateur" #4da6ff
|
boundary frontend as "Interface \nadministrateur" #4da6ff
|
||||||
participant "Back end\nanalyse de données" as backend #ff8680
|
participant "Back end\nanalyse de données" as backend #ff8680
|
||||||
database database as "Base de donnée"
|
database database as "Base de donnée"
|
||||||
|
|
||||||
database -> backend : Données brutes
|
database -> backend : Données brutes
|
||||||
backend -> frontend : Statistiques sur les données
|
backend -> frontend : Statistiques sur les données
|
||||||
utilisateur <-- frontend : données visualisées
|
utilisateur <-- frontend : données visualisées
|
||||||
....
|
....
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
=== Tableau détaillé des tâches
|
=== Tableau détaillé des tâches
|
||||||
|
|
||||||
****Note : 2 pages max - les fiches modules seront placées en annexe,
|
****Note : 2 pages max - les fiches modules seront placées en annexe,
|
||||||
elles doivent être rédigées avec l’expert.****
|
elles doivent être rédigées avec l’expert.****
|
||||||
|
|
||||||
Les différents aspects du projet sont découpés en tâches numérotées et
|
Les différents aspects du projet sont découpés en tâches numérotées et
|
||||||
hiérarchisées (Tâches/sous-tâches, etc.). Chaque tâche est décrite précisément
|
hiérarchisées (Tâches/sous-tâches, etc.). Chaque tâche est décrite précisément
|
||||||
et une équipe (typiquement un binôme) est affecté à sa réalisation. Un module
|
et une équipe (typiquement un binôme) est affecté à sa réalisation. Un module
|
||||||
est typiquement constitué de plusieurs tâches et sous-tâches.
|
est typiquement constitué de plusieurs tâches et sous-tâches.
|
||||||
|
|
||||||
Pour le PAN3, vous aurez à démontrer une version, *intégrée* et fonctionnelle de
|
Pour le PAN3, vous aurez à démontrer une version, *intégrée* et fonctionnelle de
|
||||||
votre projet.
|
votre projet.
|
||||||
Entre le PAN3 et le PAN4, vous pourrez améliorer les fonctionnalités intégrées
|
Entre le PAN3 et le PAN4, vous pourrez améliorer les fonctionnalités intégrées
|
||||||
sans pour autant en ajouter de nouvelles.
|
sans pour autant en ajouter de nouvelles.
|
||||||
|
|
||||||
Reprenez et complétez le tableau suivant en précisant les différentes tâches et
|
Reprenez et complétez le tableau suivant en précisant les différentes tâches et
|
||||||
sous-tâches ainsi que le (ou les) critères de réussite pour l'intégration au
|
sous-tâches ainsi que le (ou les) critères de réussite pour l'intégration au
|
||||||
PAN3. La définition des critères de réussite doit se faire en accord avec les
|
PAN3. La définition des critères de réussite doit se faire en accord avec les
|
||||||
experts.
|
experts.
|
||||||
|
|
||||||
[cols=",,^",options="header",]
|
[cols=",,^",options="header",]
|
||||||
|====
|
|====
|
||||||
| Tâche | Description | Critères de réussite pour l'intégration au PAN3
|
| Tâche | Description | Critères de réussite pour l'intégration au PAN3
|
||||||
| T1 | Classificateur d’image | interfaces logicielles finalisées
|
| T1 | Classificateur d’image | interfaces logicielles finalisées
|
||||||
| T1.1 | Changement espace de couleur pour permettre … | au moins une transformation implémentée
|
| T1.1 | Changement espace de couleur pour permettre … | au moins une transformation implémentée
|
||||||
| T1.2 | Extraction de l’histogramme à partir de l’espace XYZ… | …
|
| T1.2 | Extraction de l’histogramme à partir de l’espace XYZ… | …
|
||||||
| T1.3 | Extraction des caractéristiques … | …
|
| T1.3 | Extraction des caractéristiques … | …
|
||||||
| T1.4 | Comparaison des caractéristiques | …
|
| T1.4 | Comparaison des caractéristiques | …
|
||||||
| T2 | Détection de rythme (Module « Audio/Rythme ») | interfaces logicielles finalisées
|
| T2 | Détection de rythme (Module « Audio/Rythme ») | interfaces logicielles finalisées
|
||||||
| T2.1 | Découpage signal sonore en bande fréquentielle | la méthode X doit être implémentée
|
| T2.1 | Découpage signal sonore en bande fréquentielle | la méthode X doit être implémentée
|
||||||
| T2.2 | Détection du tempo | …
|
| T2.2 | Détection du tempo | …
|
||||||
| T2.3 | Changement de tempo | …
|
| T2.3 | Changement de tempo | …
|
||||||
|====
|
|====
|
||||||
|
|||||||
@@ -1,126 +1,126 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg
|
<svg
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
width="54.457047mm"
|
width="54.457047mm"
|
||||||
height="50.090965mm"
|
height="50.090965mm"
|
||||||
viewBox="0 0 54.457047 50.090966"
|
viewBox="0 0 54.457047 50.090966"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="svg8"
|
id="svg8"
|
||||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
|
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
|
||||||
sodipodi:docname="logo_pact.svg">
|
sodipodi:docname="logo_pact.svg">
|
||||||
<defs
|
<defs
|
||||||
id="defs2" />
|
id="defs2" />
|
||||||
<sodipodi:namedview
|
<sodipodi:namedview
|
||||||
id="base"
|
id="base"
|
||||||
pagecolor="#ffffff"
|
pagecolor="#ffffff"
|
||||||
bordercolor="#666666"
|
bordercolor="#666666"
|
||||||
borderopacity="1.0"
|
borderopacity="1.0"
|
||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="4.8330821"
|
inkscape:zoom="4.8330821"
|
||||||
inkscape:cx="102.91095"
|
inkscape:cx="102.91095"
|
||||||
inkscape:cy="94.660092"
|
inkscape:cy="94.660092"
|
||||||
inkscape:document-units="mm"
|
inkscape:document-units="mm"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
inkscape:document-rotation="0"
|
inkscape:document-rotation="0"
|
||||||
showgrid="false"
|
showgrid="false"
|
||||||
fit-margin-top="0"
|
fit-margin-top="0"
|
||||||
fit-margin-left="0"
|
fit-margin-left="0"
|
||||||
fit-margin-right="0"
|
fit-margin-right="0"
|
||||||
fit-margin-bottom="0"
|
fit-margin-bottom="0"
|
||||||
inkscape:window-width="1920"
|
inkscape:window-width="1920"
|
||||||
inkscape:window-height="1136"
|
inkscape:window-height="1136"
|
||||||
inkscape:window-x="1920"
|
inkscape:window-x="1920"
|
||||||
inkscape:window-y="27"
|
inkscape:window-y="27"
|
||||||
inkscape:window-maximized="1" />
|
inkscape:window-maximized="1" />
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata5">
|
id="metadata5">
|
||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
<cc:Work
|
<cc:Work
|
||||||
rdf:about="">
|
rdf:about="">
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:type
|
<dc:type
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
<dc:title />
|
<dc:title />
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
</metadata>
|
</metadata>
|
||||||
<g
|
<g
|
||||||
inkscape:label="Calque 1"
|
inkscape:label="Calque 1"
|
||||||
inkscape:groupmode="layer"
|
inkscape:groupmode="layer"
|
||||||
id="layer1"
|
id="layer1"
|
||||||
transform="translate(56.166156,-229.10014)">
|
transform="translate(56.166156,-229.10014)">
|
||||||
<g
|
<g
|
||||||
id="g873">
|
id="g873">
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:#bf0f35;stroke-width:0.0799999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:#bf0f35;stroke-width:0.0799999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m -16.977662,258.78246 c 5.940096,-1.65648 7.8816155,-7.75236 8.8150796,-13.20598 0,0 1.6478264,3.55859 2.075099,8.27172 l 4.3331292,-4.27365 c 0,0 -2.6243945,-8.72486 -8.4504458,-13.71892 0,0 -2.002139,5.21097 -6.882139,9.15869 l 1.271757,3.33099 c 3.453653,-2.20867 3.956774,-2.94163 3.956774,-2.94163 l 0.03924,0.01 c -0.32094,3.48487 -1.803649,7.09895 -3.701677,8.57574 l -1.456796,4.79441 z"
|
d="m -16.977662,258.78246 c 5.940096,-1.65648 7.8816155,-7.75236 8.8150796,-13.20598 0,0 1.6478264,3.55859 2.075099,8.27172 l 4.3331292,-4.27365 c 0,0 -2.6243945,-8.72486 -8.4504458,-13.71892 0,0 -2.002139,5.21097 -6.882139,9.15869 l 1.271757,3.33099 c 3.453653,-2.20867 3.956774,-2.94163 3.956774,-2.94163 l 0.03924,0.01 c -0.32094,3.48487 -1.803649,7.09895 -3.701677,8.57574 l -1.456796,4.79441 z"
|
||||||
id="path1733"
|
id="path1733"
|
||||||
sodipodi:nodetypes="cccccccccccc" />
|
sodipodi:nodetypes="cccccccccccc" />
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:#bf0f35;stroke-width:0.0799999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:#bf0f35;stroke-width:0.0799999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m -26.906931,237.55795 c -0.854783,-3.28229 -4.000402,-4.35511 -6.814598,-4.87092 0,0 1.836313,-0.91053 4.268405,-1.14661 l -2.205304,-2.39436 c 0,0 -4.502234,1.45018 -7.079299,4.66945 0,0 2.688989,1.10631 4.726105,3.80282 l 1.718875,-0.70267 c -1.139727,-1.90843 -1.517956,-2.18642 -1.517956,-2.18642 l 0.0048,-0.0222 c 1.798279,0.17747 3.663229,0.9966 4.425287,2.04539 l 2.47403,0.805 z"
|
d="m -26.906931,237.55795 c -0.854783,-3.28229 -4.000402,-4.35511 -6.814598,-4.87092 0,0 1.836313,-0.91053 4.268405,-1.14661 l -2.205304,-2.39436 c 0,0 -4.502234,1.45018 -7.079299,4.66945 0,0 2.688989,1.10631 4.726105,3.80282 l 1.718875,-0.70267 c -1.139727,-1.90843 -1.517956,-2.18642 -1.517956,-2.18642 l 0.0048,-0.0222 c 1.798279,0.17747 3.663229,0.9966 4.425287,2.04539 l 2.47403,0.805 z"
|
||||||
id="path2560"
|
id="path2560"
|
||||||
sodipodi:nodetypes="cccccccccccc" />
|
sodipodi:nodetypes="cccccccccccc" />
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:#bf0f35;stroke-width:0.0799999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:#bf0f35;stroke-width:0.0799999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m -44.843191,247.55123 c -4.400245,1.13299 -5.838453,5.30244 -6.529921,9.03262 0,0 -1.220665,-2.43401 -1.53717,-5.65773 l -3.209838,2.92313 c 0,0 1.944066,5.96765 6.25981,9.3835 0,0 1.483128,-3.56421 5.098066,-6.26443 L -45.704318,254.69 c -2.558356,1.51069 -2.931063,2.01208 -2.931063,2.01208 l -0.02921,-0.006 c 0.23776,-2.3836 1.336091,-4.85555 2.742077,-5.86567 l 1.079155,-3.27931 z"
|
d="m -44.843191,247.55123 c -4.400245,1.13299 -5.838453,5.30244 -6.529921,9.03262 0,0 -1.220665,-2.43401 -1.53717,-5.65773 l -3.209838,2.92313 c 0,0 1.944066,5.96765 6.25981,9.3835 0,0 1.483128,-3.56421 5.098066,-6.26443 L -45.704318,254.69 c -2.558356,1.51069 -2.931063,2.01208 -2.931063,2.01208 l -0.02921,-0.006 c 0.23776,-2.3836 1.336091,-4.85555 2.742077,-5.86567 l 1.079155,-3.27931 z"
|
||||||
id="path1733-3"
|
id="path1733-3"
|
||||||
sodipodi:nodetypes="cccccccccccc" />
|
sodipodi:nodetypes="cccccccccccc" />
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:#bf0f35;stroke-width:0.0799999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:#bf0f35;stroke-width:0.0799999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m -36.994644,265.84884 c 1.478442,5.18855 6.91915,6.88443 11.786622,7.69978 0,0 -3.176116,1.43935 -7.382689,1.81255 l 3.814321,3.78489 c 0,0 7.787125,-2.29236 12.244443,-7.38126 0,0 -4.650907,-1.74883 -8.174335,-6.0114 l -2.97298,1.11086 c 1.971287,3.01669 2.625471,3.45617 2.625471,3.45617 l -0.0086,0.0343 c -3.110321,-0.28033 -6.33597,-1.57546 -7.654034,-3.23333 l -4.279114,-1.27249 z"
|
d="m -36.994644,265.84884 c 1.478442,5.18855 6.91915,6.88443 11.786622,7.69978 0,0 -3.176116,1.43935 -7.382689,1.81255 l 3.814321,3.78489 c 0,0 7.787125,-2.29236 12.244443,-7.38126 0,0 -4.650907,-1.74883 -8.174335,-6.0114 l -2.97298,1.11086 c 1.971287,3.01669 2.625471,3.45617 2.625471,3.45617 l -0.0086,0.0343 c -3.110321,-0.28033 -6.33597,-1.57546 -7.654034,-3.23333 l -4.279114,-1.27249 z"
|
||||||
id="path1733-6"
|
id="path1733-6"
|
||||||
sodipodi:nodetypes="cccccccccccc" />
|
sodipodi:nodetypes="cccccccccccc" />
|
||||||
<path
|
<path
|
||||||
id="circle1731"
|
id="circle1731"
|
||||||
style="fill:#d7022c;fill-opacity:1;stroke:#bf0f35;stroke-width:0.302362;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
style="fill:#d7022c;fill-opacity:1;stroke:#bf0f35;stroke-width:0.302362;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
d="M 96.271484 28.513672 A 57.882072 57.882072 0 0 0 38.464844 86.394531 A 57.882072 57.882072 0 0 0 96.347656 144.27734 A 57.882072 57.882072 0 0 0 154.22852 86.394531 A 57.882072 57.882072 0 0 0 96.347656 28.513672 A 57.882072 57.882072 0 0 0 96.271484 28.513672 z M 92.191406 43.785156 A 38.455465 38.455465 0 0 1 130.64844 82.240234 A 38.455465 38.455465 0 0 1 92.191406 120.69531 A 38.455465 38.455465 0 0 1 53.736328 82.240234 A 38.455465 38.455465 0 0 1 92.191406 43.785156 z "
|
d="M 96.271484 28.513672 A 57.882072 57.882072 0 0 0 38.464844 86.394531 A 57.882072 57.882072 0 0 0 96.347656 144.27734 A 57.882072 57.882072 0 0 0 154.22852 86.394531 A 57.882072 57.882072 0 0 0 96.347656 28.513672 A 57.882072 57.882072 0 0 0 96.271484 28.513672 z M 92.191406 43.785156 A 38.455465 38.455465 0 0 1 130.64844 82.240234 A 38.455465 38.455465 0 0 1 92.191406 120.69531 A 38.455465 38.455465 0 0 1 53.736328 82.240234 A 38.455465 38.455465 0 0 1 92.191406 43.785156 z "
|
||||||
transform="matrix(0.26458333,0,0,0.26458333,-56.166156,229.10014)" />
|
transform="matrix(0.26458333,0,0,0.26458333,-56.166156,229.10014)" />
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
d="m -45.863207,254.98309 0.310939,-0.0832 0.564065,2.10514 -0.371041,0.2143 -0.57429,-2.14324 z"
|
d="m -45.863207,254.98309 0.310939,-0.0832 0.564065,2.10514 -0.371041,0.2143 -0.57429,-2.14324 z"
|
||||||
id="path2593" />
|
id="path2593" />
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
d="m -45.300571,248.03802 -0.342908,-0.10985 -0.685015,2.62714 0.405979,0.27462 0.697428,-2.6746 z"
|
d="m -45.300571,248.03802 -0.342908,-0.10985 -0.685015,2.62714 0.405979,0.27462 0.697428,-2.6746 z"
|
||||||
id="path2593-7" />
|
id="path2593-7" />
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
d="m -34.355934,237.12764 0.0044,-0.3177 1.677308,-0.27653 0.153882,0.39548 -1.779755,0.28383 z"
|
d="m -34.355934,237.12764 0.0044,-0.3177 1.677308,-0.27653 0.153882,0.39548 -1.779755,0.28383 z"
|
||||||
id="path2593-5"
|
id="path2593-5"
|
||||||
sodipodi:nodetypes="cccccc" />
|
sodipodi:nodetypes="cccccc" />
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
d="m -29.292985,236.37594 -0.03432,0.50987 2.147941,0.39307 c 0.0861,-0.0473 -0.126388,-0.4538 -0.126388,-0.4538 l -1.898806,-0.45393 z"
|
d="m -29.292985,236.37594 -0.03432,0.50987 2.147941,0.39307 c 0.0861,-0.0473 -0.126388,-0.4538 -0.126388,-0.4538 l -1.898806,-0.45393 z"
|
||||||
id="path2593-3"
|
id="path2593-3"
|
||||||
sodipodi:nodetypes="cccccc" />
|
sodipodi:nodetypes="cccccc" />
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
d="m -17.005391,245.04429 0.671446,-0.11334 1.269369,2.76702 -1.149127,0.7229 -0.943557,-3.25 z"
|
d="m -17.005391,245.04429 0.671446,-0.11334 1.269369,2.76702 -1.149127,0.7229 -0.943557,-3.25 z"
|
||||||
id="path2593-56"
|
id="path2593-56"
|
||||||
sodipodi:nodetypes="cccccc" />
|
sodipodi:nodetypes="cccccc" />
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
d="m -15.547639,254.1138 -0.766385,-0.26033 -1.144876,5.0476 0.995934,-0.47717 1.294606,-4.48138 z"
|
d="m -15.547639,254.1138 -0.766385,-0.26033 -1.144876,5.0476 0.995934,-0.47717 1.294606,-4.48138 z"
|
||||||
id="path2593-2"
|
id="path2593-2"
|
||||||
sodipodi:nodetypes="cccccc" />
|
sodipodi:nodetypes="cccccc" />
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
d="m -37.137503,265.08378 0.578189,1.5625 3.862238,0.7537 -0.309796,-0.64182 -3.278461,-0.97977 z"
|
d="m -37.137503,265.08378 0.578189,1.5625 3.862238,0.7537 -0.309796,-0.64182 -3.278461,-0.97977 z"
|
||||||
id="path2593-9"
|
id="path2593-9"
|
||||||
sodipodi:nodetypes="cccccc" />
|
sodipodi:nodetypes="cccccc" />
|
||||||
<path
|
<path
|
||||||
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
style="fill:#d7022c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
d="m -27.284445,267.18565 -0.309066,-0.4019 2.772667,-1.04584 0.393632,0.61369 -2.143236,0.57428 z"
|
d="m -27.284445,267.18565 -0.309066,-0.4019 2.772667,-1.04584 0.393632,0.61369 -2.143236,0.57428 z"
|
||||||
id="path2593-1"
|
id="path2593-1"
|
||||||
sodipodi:nodetypes="cccccc" />
|
sodipodi:nodetypes="cccccc" />
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 8.0 KiB |