This commit is contained in:
Louis Mazin 2025-07-10 19:07:20 +02:00
parent 73cc0e3ca9
commit 6c69ab37ae
11 changed files with 389 additions and 61 deletions

6
.gitignore vendored
View File

@ -20,9 +20,9 @@ desktop.ini
.env.development .env.development
.env.test .env.test
.env.production .env.production
LINenv/ LINenv*/
WINenv/ WINenv*/
MACenv/ MACenv*/
# Fichiers de dépendances # Fichiers de dépendances
/node_modules/ /node_modules/

View File

@ -4,7 +4,6 @@ setlocal enabledelayedexpansion
REM === PATH SETUP === REM === PATH SETUP ===
set PARENT_DIR=%~dp0 set PARENT_DIR=%~dp0
set BUILD_DIR=%~dp0.. set BUILD_DIR=%~dp0..
set VENV_PATH=%PARENT_DIR%\WINenv
set PYTHON_IN_VENV=%VENV_PATH%\Scripts\python.exe set PYTHON_IN_VENV=%VENV_PATH%\Scripts\python.exe
set ICON_PATH=data/assets/icon.ico set ICON_PATH=data/assets/icon.ico
set CONFIG_FILE=%PARENT_DIR%\config.json set CONFIG_FILE=%PARENT_DIR%\config.json
@ -16,7 +15,7 @@ for /f "delims=" %%i in ('powershell -NoProfile -Command ^
"Get-Content '%CONFIG_FILE%' | ConvertFrom-Json | Select-Object -ExpandProperty app_name"') do set APP_NAME=%%i "Get-Content '%CONFIG_FILE%' | ConvertFrom-Json | Select-Object -ExpandProperty app_name"') do set APP_NAME=%%i
for /f "delims=" %%i in ('powershell -NoProfile -Command ^ for /f "delims=" %%i in ('powershell -NoProfile -Command ^
"Get-Content '%CONFIG_FILE%' | ConvertFrom-Json | Select-Object -ExpandProperty architecture"') do set ARCHITECTURE=%%i "Get-Content '%CONFIG_FILE%' | ConvertFrom-Json | Select-Object -ExpandProperty architecture"') do set ARCHITECTURE=%%i
set VENV_PATH=%PARENT_DIR%\WINenv_%ARCHITECTURE%
set EXE_NAME=%APP_NAME%-Windows-%ARCHITECTURE% set EXE_NAME=%APP_NAME%-Windows-%ARCHITECTURE%
REM === Construct full python path from config.json === REM === Construct full python path from config.json ===

View File

@ -3,5 +3,5 @@
"python_version": "3.11.7", "python_version": "3.11.7",
"app_os": "Windows", "app_os": "Windows",
"app_version": "1.0.0", "app_version": "1.0.0",
"architecture": "x64" "architecture": "x32"
} }

106
core/theme.py Normal file
View File

@ -0,0 +1,106 @@
def get_element_color(element: str) -> str:
if element == "background":
return "#FFFFFF"
if element == "background2":
return "#F5F5F5"
if element == "background3":
return "#E0E0E0"
if element == "color":
return "#5D5A5A"
if element == "text":
return "#000000"
else:
return "#000000"
def get_sheet() -> str:
return f"""
QWidget {{
background-color: {get_element_color("background")};
color: {get_element_color("text")};
}}
QPushButton {{
background-color: #0A84FF;
color: {get_element_color("text")};
border-radius: 8px;
font-size: 16px;
padding: 10px 20px;
border: none;
}}
QPushButton:hover {{
background-color: #007AFF;
}}
QLineEdit {{
border: 1px solid #3C3C3E;
border-radius: 8px;
padding: 10px;
font-size: 14px;
background-color: {get_element_color("background2")};
color: {get_element_color("text")};
}}
QLabel {{
color: {get_element_color("text")};
}}
QProgressBar {{
border: 1px solid #3C3C3E;
border-radius: 5px;
background-color: {get_element_color("background2")};
text-align: center;
color: {get_element_color("text")};
}}
QProgressBar::chunk {{
background-color: #0A84FF;
border-radius: 3px;
}}
QFrame {{
background-color: {get_element_color("background2")};
border: none;
}}
QFrame#indicator_bar {{
background-color: #0A84FF;
}}
QScrollBar:vertical {{
border: none;
background: #E0E0E0;
width: 8px;
margin: 0px;
}}
QScrollBar::handle:vertical {{
background: #0A84FF;
border-radius: 4px;
min-height: 20px;
}}
QScrollBar::handle:vertical:hover {{
background: #3B9CFF;
}}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{
border: none;
background: none;
height: 0px;
}}
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {{
background: none;
}}
#drag_area {{
border: 2px dashed #3498db; border-radius: 10px;
}}
QSlider::groove:horizontal {{
border: 1px solid #3a9bdc;
height: 10px;
background: transparent;
border-radius: 5px;
}}
QSlider::sub-page:horizontal {{
background: #3a9bdc;
border-radius: 5px;
}}
QSlider::add-page:horizontal {{
background: {get_element_color("background3")};
border-radius: 5px;
}}
QSlider::handle:horizontal {{
background: white;
border: 2px solid #3a9bdc;
width: 14px;
margin: -4px 0;
border-radius: 7px;
}}
"""

83
core/verifier.py Normal file
View File

@ -0,0 +1,83 @@
import json, csv
from utils.paths import resource_path
class Patient:
def __init__(self, json_data : dict):
self.json_data = json_data
def get_value(self, key):
return self.json_data.get(key, None)
def compare_value(self, key, value):
return self.get_value(key) == value
def __str__(self):
return f"Patient({self.json_data})"
class Patients:
def __init__(self, patients_data : list[dict]):
self.patients = [Patient(data) for data in patients_data]
def get_patients(self):
return self.patients
def trouver(self, to_find):
for patient in self.patients:
if patient.get_value("Numéro de correspondance") == to_find.get_value("Numéro de correspondance"):
return patient
return None
class Verifier:
def __init__(self):
try:
truth_file_path = resource_path("data/truth.json")
with open(truth_file_path, 'r', encoding='utf-8') as file:
truth_data = json.load(file)
self.truth = Patients(truth_data["patients"])
except Exception:
self.truth = Patients([])
def verify(self, patients_csv: str):
# Read CSV file and convert to list of dictionaries
patients_data = []
try:
with open(patients_csv, 'r', encoding='utf-8') as file:
csv_reader = csv.reader(file, delimiter=';')
rows = list(csv_reader)
if not rows:
return "CSV file is empty"
# First row contains column names
headers = rows[0]
# Convert each subsequent row to a dictionary
for row in rows[1:]:
patient_dict = {headers[i]: row[i] for i in range(len(headers))}
patients_data.append(patient_dict)
except FileNotFoundError:
return f"Error: File {patients_csv} not found"
except Exception as e:
return f"Error reading CSV file: {e}"
self.patients = Patients(patients_data)
# Check if we have truth data
if not self.truth.get_patients():
return ["Erreur: Aucune donnée de vérité n'a pu être chargée"]
key_to_test = self.truth.get_patients()[0].json_data.keys()
issues = []
for patient in self.patients.get_patients():
issue_founded = False
truth_patient = self.truth.trouver(patient)
if truth_patient:
for key in key_to_test:
if not patient.compare_value(key, truth_patient.get_value(key)):
issue_founded = True
issues.append(f"Le patient avec le numéro de correspondance n°{patient.get_value('Numéro de correspondance')} a une discordance avec {key}. Attendu : {truth_patient.get_value(key)}, Trouvé : {patient.get_value(key)}")
else:
issue_founded = True
issues.append(f"Le patient avec le numéro de correspondance n°{patient.get_value('Numéro de correspondance')} n'a pas été trouvé dans les données de vérité")
if issue_founded:
issues.append("")
return issues

20
data/truth.json Normal file
View File

@ -0,0 +1,20 @@
{
"patients": [
{
"Numéro de correspondance": "0",
"date_de_la_pose_de_l_endoprothese": "2023-10-01"
},
{
"Numéro de correspondance": "1",
"date_de_la_pose_de_l_endoprothese": "2023-10-02"
},
{
"Numéro de correspondance": "2",
"date_de_la_pose_de_l_endoprothese": "2023-10-03"
},
{
"Numéro de correspondance": "3",
"date_de_la_pose_de_l_endoprothese": "2023-10-04"
}
]
}

58
main.py
View File

@ -1,53 +1,15 @@
## Interface with only a path widget (combo of QLineEdit and QPushButton to explore the file system)
## and a button : verify (call a temp verify function)
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLineEdit, QFileDialog
import sys import sys
import os from PyQt5.QtWidgets import QApplication
import tempfile from ui.windows.main import Main
from core.theme import get_sheet
def verify_path(path): def main():
# Temporary verification function app = QApplication(sys.argv)
if os.path.exists(path): app.setStyleSheet(get_sheet())
print(f"Path '{path}' exists.") window = Main()
else:
print(f"Path '{path}' does not exist.")
class PathWidget(QWidget): window.show()
def __init__(self): return app.exec()
super().__init__()
self.init_ui()
def init_ui(self):
self.layout = QVBoxLayout()
# Create a QLineEdit for the path input
self.path_input = QLineEdit(self)
self.path_input.setPlaceholderText("Enter or select a path")
self.layout.addWidget(self.path_input)
# Create a QPushButton to open the file dialog
self.browse_button = QPushButton("Browse", self)
self.browse_button.clicked.connect(self.open_file_dialog)
self.layout.addWidget(self.browse_button)
# Create a QPushButton to verify the path
self.verify_button = QPushButton("Verify", self)
self.verify_button.clicked.connect(self.verify_path)
self.layout.addWidget(self.verify_button)
self.setLayout(self.layout)
self.setWindowTitle("Path Widget Example")
def open_file_dialog(self):
path, _ = QFileDialog.getExistingDirectory(self, "Select Directory")
if path:
self.path_input.setText(path)
def verify_path(self):
verify_path(self.path_input.text())
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) sys.exit(main())
widget = PathWidget()
widget.show()
sys.exit(app.exec_())

View File

@ -4,8 +4,6 @@ setlocal enabledelayedexpansion
REM Set file paths REM Set file paths
set CONFIG_FILE=%~dp0config.json set CONFIG_FILE=%~dp0config.json
set REQUIREMENTS=requirements.txt set REQUIREMENTS=requirements.txt
set ENV_NAME=WINenv
set ENV_PATH=%~dp0%ENV_NAME%
REM Extract config.json fields using PowerShell REM Extract config.json fields using PowerShell
for /f "delims=" %%i in ('powershell -NoProfile -Command ^ for /f "delims=" %%i in ('powershell -NoProfile -Command ^
@ -16,7 +14,8 @@ for /f "delims=" %%i in ('powershell -NoProfile -Command ^
REM Construct python executable path REM Construct python executable path
set PYTHON_EXEC=C:\Logiciels\Python\%ARCHITECTURE%\%PYTHON_VERSION%\python.exe set PYTHON_EXEC=C:\Logiciels\Python\%ARCHITECTURE%\%PYTHON_VERSION%\python.exe
set ENV_NAME=WINenv_%ARCHITECTURE%
set ENV_PATH=%~dp0%ENV_NAME%
if not exist "%PYTHON_EXEC%" ( if not exist "%PYTHON_EXEC%" (
echo [ERROR] Python introuvable à: %PYTHON_EXEC% echo [ERROR] Python introuvable à: %PYTHON_EXEC%
echo Veuillez vérifier votre config.json ou le dossier d'installation. echo Veuillez vérifier votre config.json ou le dossier d'installation.
@ -32,10 +31,9 @@ REM Check if virtual environment exists
if not exist "%ENV_PATH%\Scripts\activate.bat" ( if not exist "%ENV_PATH%\Scripts\activate.bat" (
echo [INFO] Environnement virtuel introuvable, création... echo [INFO] Environnement virtuel introuvable, création...
"%PYTHON_EXEC%" -m venv "%ENV_NAME%" "%PYTHON_EXEC%" -m venv "%ENV_NAME%"
call "%ENV_PATH%\Scripts\activate.bat"
echo [INFO] Installation des dépendances... echo [INFO] Installation des dépendances...
pip install --upgrade pip "%ENV_PATH%\Scripts\pip" install --upgrade pip
pip install -r "%REQUIREMENTS%" "%ENV_PATH%\Scripts\pip" install -r "%REQUIREMENTS%"
) else ( ) else (
echo [INFO] Environnement virtuel trouvé. echo [INFO] Environnement virtuel trouvé.
) )

76
toTest.csv Normal file

File diff suppressed because one or more lines are too long

78
ui/windows/main.py Normal file
View File

@ -0,0 +1,78 @@
from PyQt5.QtGui import QIcon
import utils.paths as paths
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QFileDialog, QMainWindow, QWidget, QTextEdit
from core.verifier import Verifier # Assuming Verifier is defined in core.verifier
class Main(QMainWindow):
def __init__(self):
super().__init__()
self.verifier = Verifier()
# Configurer la fenêtre avant de créer les widgets
self.setMinimumSize(1000, 600)
self.setWindowTitle("HoDA_Verifier")
self.setWindowIcon(QIcon(paths.get_asset_path("icon")))
self.setup_ui()
def setup_ui(self):
# Create a central widget
central_widget = QWidget()
self.main_layout = QVBoxLayout(central_widget)
self.main_layout.setSpacing(20)
self.browse_layout = QHBoxLayout()
self.browse_layout.setSpacing(10)
# Create a QLineEdit for the path input
self.path_input = QLineEdit(self)
self.path_input.setPlaceholderText("Sélectionnes le fichier csv ->")
self.browse_layout.addWidget(self.path_input)
# Create a QPushButton to open the file dialog
self.browse_button = QPushButton("Parcourir", self)
self.browse_button.clicked.connect(self.open_file_dialog)
self.browse_layout.addWidget(self.browse_button)
# Create a QPushButton to verify the path
self.verify_button = QPushButton("Vérifier", self)
self.verify_button.clicked.connect(self.verify_file)
self.main_layout.addLayout(self.browse_layout)
# Create a QTextEdit for displaying verification results
self.results_display = QTextEdit(self)
self.results_display.setPlaceholderText("Les résultats de la vérification apparaîtront ici...")
self.results_display.setReadOnly(True) # Make it read-only
self.results_display.setMinimumHeight(200) # Set minimum height
self.main_layout.addWidget(self.results_display)
self.main_layout.addWidget(self.verify_button)
# Set the layout on the central widget
central_widget.setLayout(self.main_layout)
# Set the central widget
self.setCentralWidget(central_widget)
def open_file_dialog(self):
path, _ = QFileDialog.getOpenFileName(self, "Select File", "", "CSV Files (*.csv)")
if path:
self.path_input.setText(path)
def verify_file(self):
file_path = self.path_input.text()
if not file_path:
self.results_display.setText("Veuillez sélectionner un fichier CSV d'abord.")
return
result = self.verifier.verify(file_path)
# Clear previous results
self.results_display.clear()
if isinstance(result, list):
if result: # If there are issues
# Join the list of issues with newlines
self.results_display.setText("\n".join(result))
else: # No issues found
self.results_display.setText("✅ Aucune discordance trouvée ! Toutes les données correspondent.")
else: # If result is a string (error message)
self.results_display.setText(result)

View File

@ -13,4 +13,10 @@ def resource_path(relative_path: str) -> Path:
except AttributeError: except AttributeError:
base_path = Path(__file__).parent.parent # Dev environment: source/ folder base_path = Path(__file__).parent.parent # Dev environment: source/ folder
return path.join(base_path, relative_path) return path.join(base_path, relative_path)
def get_data_dir() -> Path:
return resource_path("data")
def get_asset_path(asset: str) -> Path:
return path.join(get_data_dir(), "assets", f"{asset}.png")