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.test
.env.production
LINenv/
WINenv/
MACenv/
LINenv*/
WINenv*/
MACenv*/
# Fichiers de dépendances
/node_modules/

View File

@ -4,7 +4,6 @@ setlocal enabledelayedexpansion
REM === PATH SETUP ===
set PARENT_DIR=%~dp0
set BUILD_DIR=%~dp0..
set VENV_PATH=%PARENT_DIR%\WINenv
set PYTHON_IN_VENV=%VENV_PATH%\Scripts\python.exe
set ICON_PATH=data/assets/icon.ico
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
for /f "delims=" %%i in ('powershell -NoProfile -Command ^
"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%
REM === Construct full python path from config.json ===

View File

@ -3,5 +3,5 @@
"python_version": "3.11.7",
"app_os": "Windows",
"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 os
import tempfile
from PyQt5.QtWidgets import QApplication
from ui.windows.main import Main
from core.theme import get_sheet
def verify_path(path):
# Temporary verification function
if os.path.exists(path):
print(f"Path '{path}' exists.")
else:
print(f"Path '{path}' does not exist.")
def main():
app = QApplication(sys.argv)
app.setStyleSheet(get_sheet())
window = Main()
class PathWidget(QWidget):
def __init__(self):
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())
window.show()
return app.exec()
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = PathWidget()
widget.show()
sys.exit(app.exec_())
sys.exit(main())

View File

@ -4,8 +4,6 @@ setlocal enabledelayedexpansion
REM Set file paths
set CONFIG_FILE=%~dp0config.json
set REQUIREMENTS=requirements.txt
set ENV_NAME=WINenv
set ENV_PATH=%~dp0%ENV_NAME%
REM Extract config.json fields using PowerShell
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
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%" (
echo [ERROR] Python introuvable à: %PYTHON_EXEC%
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" (
echo [INFO] Environnement virtuel introuvable, création...
"%PYTHON_EXEC%" -m venv "%ENV_NAME%"
call "%ENV_PATH%\Scripts\activate.bat"
echo [INFO] Installation des dépendances...
pip install --upgrade pip
pip install -r "%REQUIREMENTS%"
"%ENV_PATH%\Scripts\pip" install --upgrade pip
"%ENV_PATH%\Scripts\pip" install -r "%REQUIREMENTS%"
) else (
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:
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")