ParcoursOuvrir l'appli

Le rapport final

Projet : analyser des ventes · 60 min

Des chiffres lisibles

Mama Brigitte ne lit pas de dictionnaires Python. Il lui faut une page claire, qu'elle peut lire, imprimer ou envoyer par WhatsApp. Dernière étape : présenter.

Deux outils de présentation pour commencer.

Séparer les milliers

142050 se lit mal ; 142 050 se lit tout de suite. Dans une f-string, {x:,} ajoute une virgule entre les milliers, à l'anglaise : f"{142050:,}" donne "142,050". En français, on sépare par une espace : il suffit de remplacer la virgule.

montant = 142050
print(f"{montant:,}".replace(",", " "))    # 142 050

Aligner en colonnes

print(f"{'riz':<10}|")      # riz       |
print(f"{34800:>8}|")       #    34800|

Règle des tableaux bien faits : le texte s'aligne à gauche, les nombres à droite, pour que les unités, dizaines et centaines tombent les unes sous les autres.

Un petit tableau aligné

Lance, puis change les largeurs (10, 9) pour voir l'effet.

totaux = {"sucre": 40500, "riz": 34800, "spaghetti": 8800}

for produit, montant in totaux.items():
    joli = f"{montant:,}".replace(",", " ")
    print(f"{produit:<10}{joli:>9} FCFA")

Exercice : Brique : écrire un montant

Écris fcfa(montant) qui renvoie le montant avec une espace entre les milliers, suivi de FCFA. Par exemple fcfa(142050) renvoie "142 050 FCFA".

Voir une solution
def fcfa(montant):
    """Renvoie le montant avec les milliers séparés, suivi de FCFA."""
    return f"{montant:,}".replace(",", " ") + " FCFA"

Dans l'appli, ton code est exécuté et corrigé automatiquement. Faire l'exercice

Exercice : Brique : un tableau trié

Écris tableau(totaux) qui affiche une ligne par clé, de la plus grande valeur à la plus petite. Chaque ligne contient : la clé sur 12 caractères alignée à gauche, puis le montant (avec fcfa, qui est donnée) sur 14 caractères aligné à droite.

Pour {"Awa": 39150, "Paul": 64300, "Mireille": 38600}, l'affichage doit être :

Paul           64 300 FCFA
Awa            39 150 FCFA
Mireille       38 600 FCFA
Voir une solution
def fcfa(montant):
    """Renvoie le montant avec les milliers séparés, suivi de FCFA."""
    return f"{montant:,}".replace(",", " ") + " FCFA"


def tableau(totaux):
    """Affiche les totaux du plus grand au plus petit, en colonnes alignées."""
    for nom, montant in sorted(totaux.items(), key=lambda paire: paire[1], reverse=True):
        print(f"{nom:<12}{fcfa(montant):>14}")


tableau({"Awa": 39150, "Paul": 64300, "Mireille": 38600})

Dans l'appli, ton code est exécuté et corrigé automatiquement. Faire l'exercice

Écrire dans un fichier

Afficher le rapport, c'est bien. L'enregistrer dans un fichier qu'on peut envoyer, c'est mieux. On ouvre un fichier en écriture en ajoutant un deuxième argument à open :

with open("rapport.txt", "w", encoding="utf-8") as f:
    f.write("RAPPORT DES VENTES\n")
    f.write("Chiffre d'affaires : 142 050 FCFA\n")

Une technique pratique pour un rapport : construire d'abord la liste des lignes, puis l'afficher et l'écrire. Pour coller les lignes avec des retours à la ligne, on utilise .join :

lignes = ["RAPPORT", "Ventes : 42"]
texte = "\n".join(lignes)
print(texte)
with open("rapport.txt", "w", encoding="utf-8") as f:
    f.write(texte + "\n")

Exercice : Brique : enregistrer le rapport

Écris enregistrer(lignes, nom_fichier) qui écrit toutes les lignes de la liste dans le fichier, une par ligne, avec un retour à la ligne à la fin de chacune.

Voir une solution
def enregistrer(lignes, nom_fichier):
    """Écrit les lignes dans le fichier, une par ligne."""
    with open(nom_fichier, "w", encoding="utf-8") as f:
        f.write("\n".join(lignes) + "\n")


enregistrer(["RAPPORT DES VENTES", "Ventes : 42"], "rapport.txt")

with open("rapport.txt", encoding="utf-8") as f:
    print(f.read())

Dans l'appli, ton code est exécuté et corrigé automatiquement. Faire l'exercice

Assembler le programme complet

Toutes les briques sont prêtes. Le programme final les assemble dans une fonction rapport(nom_fichier) qui :

  1. lit et nettoie le fichier avec lire_ventes_propres ;
  2. calcule les indicateurs avec chiffre_affaires, ca_par et meilleur ;
  3. construit la liste des lignes du rapport ;
  4. l'affiche, l'enregistre dans rapport.txt, et renvoie la liste.

Le rapport attendu, pour ventes.csv :

RAPPORT DES VENTES : ventes.csv
Ventes : 42 (0 ligne rejetée)
Chiffre d'affaires : 142 050 FCFA
Panier moyen : 3 382 FCFA
Meilleur produit : sucre
Meilleur vendeur : Paul
Meilleur mois : 2026-03

Par produit
sucre          40 500 FCFA   28.5 %
riz            34 800 FCFA   24.5 %
huile          28 500 FCFA   20.1 %
savon          21 000 FCFA   14.8 %
spaghetti       8 800 FCFA    6.2 %
lait            8 450 FCFA    5.9 %

Une ligne du tableau : le produit sur 12 caractères à gauche, le montant (fcfa) sur 14 à droite, puis la part {p:>7.1f} % (7 caractères à droite, 1 chiffre après la virgule). On peut combiner alignement et décimales dans la même accolade.

La ligne vide entre les indicateurs et le tableau est un texte vide "" dans la liste. Accorde « ligne rejetée » : au singulier pour 0 ou 1, au pluriel à partir de 2 (3 lignes rejetées).

Exercice : Le programme complet : rapport

Toutes les briques des cours précédents sont données. Écris rapport(nom_fichier), qui produit le rapport décrit à l'étape précédente : elle l'affiche, l'enregistre dans rapport.txt et renvoie la liste des lignes.

Le correcteur l'essaiera aussi sur le fichier d'avril, plein d'erreurs, où 3 lignes sont rejetées.

Voir une solution
def nettoyer_texte(texte):
    """Enlève les espaces autour et met en minuscules."""
    return texte.strip().lower()


def convertir(texte):
    """Renvoie le nombre entier écrit dans texte, ou None s'il n'y en a pas."""
    try:
        return int(texte.replace(" ", ""))
    except ValueError:
        return None


def lire_ventes_propres(nom_fichier):
    """Lit un CSV de ventes, nettoie chaque ligne et met de côté les lignes invalides."""
    with open(nom_fichier, encoding="utf-8") as f:
        lignes = f.read().splitlines()
    ventes = []
    rejetees = []
    for ligne in lignes[1:]:
        if ligne.strip() == "":
            continue
        morceaux = ligne.split(",")
        if len(morceaux) != 5:
            rejetees.append(ligne)
            continue
        date, produit, quantite, prix, vendeur = morceaux
        quantite = convertir(quantite)
        prix = convertir(prix)
        if quantite is None or prix is None:
            rejetees.append(ligne)
            continue
        ventes.append({
            "date": date.strip(),
            "produit": nettoyer_texte(produit),
            "quantite": quantite,
            "prix": prix,
            "vendeur": vendeur.strip(),
        })
    return ventes, rejetees


def chiffre_affaires(ventes):
    """Renvoie la somme de quantite x prix sur toutes les ventes."""
    total = 0
    for v in ventes:
        total += v["quantite"] * v["prix"]
    return total


def ca_par(ventes, cle):
    """Renvoie un dictionnaire : pour chaque valeur de la clé, son chiffre d'affaires."""
    totaux = {}
    for v in ventes:
        totaux[v[cle]] = totaux.get(v[cle], 0) + v["quantite"] * v["prix"]
    return totaux


def meilleur(totaux):
    """Renvoie la clé qui a la plus grande valeur."""
    return max(totaux, key=totaux.get)


def panier_moyen(ventes):
    """Renvoie le montant moyen d'une vente, arrondi à l'entier."""
    if len(ventes) == 0:
        return 0
    return round(chiffre_affaires(ventes) / len(ventes))


def fcfa(montant):
    """Renvoie le montant avec les milliers séparés, suivi de FCFA."""
    return f"{montant:,}".replace(",", " ") + " FCFA"


def enregistrer(lignes, nom_fichier):
    """Écrit les lignes dans le fichier, une par ligne."""
    with open(nom_fichier, "w", encoding="utf-8") as f:
        f.write("\n".join(lignes) + "\n")


def rapport(nom_fichier):
    """Lit le fichier, calcule les indicateurs, affiche et enregistre le rapport."""
    ventes, rejetees = lire_ventes_propres(nom_fichier)
    ca = chiffre_affaires(ventes)
    lignes = [f"RAPPORT DES VENTES : {nom_fichier}"]

    produits = ca_par(ventes, "produit")
    vendeurs = ca_par(ventes, "vendeur")
    mois = {}
    for v in ventes:
        mois[v["date"][:7]] = mois.get(v["date"][:7], 0) + v["quantite"] * v["prix"]

    mot = "ligne rejetée" if len(rejetees) <= 1 else "lignes rejetées"
    lignes.append(f"Ventes : {len(ventes)} ({len(rejetees)} {mot})")
    lignes.append(f"Chiffre d'affaires : {fcfa(ca)}")
    lignes.append(f"Panier moyen : {fcfa(panier_moyen(ventes))}")
    lignes.append(f"Meilleur produit : {meilleur(produits)}")
    lignes.append(f"Meilleur vendeur : {meilleur(vendeurs)}")
    lignes.append(f"Meilleur mois : {meilleur(mois)}")
    lignes.append("")
    lignes.append("Par produit")
    for nom, montant in sorted(produits.items(), key=lambda paire: paire[1], reverse=True):
        p = montant / ca * 100
        lignes.append(f"{nom:<12}{fcfa(montant):>14}{p:>7.1f} %")

    print("\n".join(lignes))
    enregistrer(lignes, "rapport.txt")
    return lignes


rapport("ventes.csv")

Dans l'appli, ton code est exécuté et corrigé automatiquement. Faire l'exercice

Le mettre sur ton GitHub

Ce projet mérite d'être dans ton dépôt d'apprentissage ml-parcours : c'est un vrai programme, que n'importe qui peut lire et relancer. Sur ton PC, avec ce que tu as appris dans les modules Terminal et Git :

cd ml-parcours
mkdir projet-ventes
cd projet-ventes
  1. Crée ventes.csv et colle les données (tu peux les copier depuis l'aperçu du fichier dans les exercices).
  2. Crée analyse.py, colle ton programme complet, et lance-le : python3 analyse.py. Le fichier rapport.txt apparaît dans le dossier.
  3. Ajoute un README.md de trois lignes : ce que fait le programme, comment le lancer, un extrait du rapport.
  4. Enregistre et publie :
git add .
git commit -m "Projet : analyse des ventes d'une boutique"
git push

Teste-toi

rapport.txt contient 20 lignes. On exécute with open("rapport.txt", "w") as f: f.write("Bonjour"). Que contient le fichier ensuite ?
  1. Les 20 lignes, puis Bonjour
  2. Seulement Bonjour (bonne réponse)
  3. Bonjour, puis les 20 lignes
  4. Une erreur, car le fichier existe déjà