{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# TP Apache Spark avec Python — notebook de départ\n",
    "\n",
    "Suivez le guide en ligne, étape par étape. Chaque étape du guide a ici son titre et une cellule vide : tapez-y votre code, puis exécutez-la avec `Maj + Entrée`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Partie 0 — Mise en place\n",
    "\n",
    "Installer PySpark, récupérer les données et démarrer Spark."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 0.1 — Ouvrir un notebook"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 0.2 — Installer PySpark"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 0.3 — Vérifier l'installation"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 0.4 — Récupérer les données"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    "# Exécutez cette cellule : elle crée le dossier data/\n",
    "import csv\n",
    "import os\n",
    "import random\n",
    "from datetime import date, timedelta\n",
    "\n",
    "TEXTE = \"\"\"Apache Spark est un moteur de calcul distribué.\n",
    "Spark permet de traiter de grandes quantités de données sur plusieurs machines.\n",
    "Avec Spark, les données sont découpées en partitions.\n",
    "Chaque partition est traitée en parallèle par un executor.\n",
    "Le driver coordonne le travail et distribue les tâches aux executors.\n",
    "Les données restent en mémoire, ce qui rend Spark rapide.\n",
    "Un RDD est une collection distribuée et immuable de données.\n",
    "Un DataFrame est une table distribuée avec des colonnes nommées.\n",
    "Les transformations décrivent un calcul, les actions déclenchent le calcul.\n",
    "Spark est paresseux : rien ne se passe avant une action.\n",
    "On peut interroger les données avec Python ou avec SQL.\n",
    "Le langage Python est très utilisé pour analyser des données.\n",
    "PySpark est la bibliothèque Python pour utiliser Spark.\n",
    "Les données de ventes permettent de mesurer le chiffre d'affaires.\n",
    "Les données des clients permettent de mieux comprendre les ventes.\n",
    "Analyser les données aide une entreprise à prendre de meilleures décisions.\n",
    "Une petite boutique a peu de données, une grande plateforme en a énormément.\n",
    "Quand les données ne tiennent plus sur une machine, Spark devient utile.\n",
    "Spark fonctionne aussi très bien sur un simple ordinateur portable.\n",
    "Bravo : vous savez maintenant compter les mots avec Spark !\n",
    "\"\"\"\n",
    "\n",
    "CATALOGUE = {\n",
    "    \"Informatique\": [(\"Clavier\", 29.90), (\"Souris\", 19.90), (\"Écran\", 179.00), (\"Casque\", 59.50), (\"Clé USB\", 12.00)],\n",
    "    \"Maison\": [(\"Lampe\", 24.99), (\"Cafetière\", 49.90), (\"Coussin\", 15.00)],\n",
    "    \"Mobilier\": [(\"Chaise\", 89.00), (\"Bureau\", 199.00), (\"Étagère\", 65.00)],\n",
    "    \"Livres\": [(\"Roman\", 18.50), (\"BD\", 14.90), (\"Livre de cuisine\", 25.00)],\n",
    "    \"Sport\": [(\"Ballon\", 22.00), (\"Tapis de yoga\", 30.00), (\"Gourde\", 11.50)],\n",
    "}\n",
    "VILLES = [\"Paris\", \"Lyon\", \"Marseille\", \"Toulouse\", \"Lille\", \"Bordeaux\", \"Nantes\", \"Strasbourg\"]\n",
    "PRENOMS = [\"Camille\", \"Léa\", \"Louis\", \"Hugo\", \"Emma\", \"Nina\", \"Adam\", \"Inès\", \"Lucas\", \"Sarah\",\n",
    "           \"Yanis\", \"Chloé\", \"Nathan\", \"Jade\", \"Rayan\", \"Manon\", \"Noah\", \"Lina\", \"Tom\", \"Zoé\"]\n",
    "PAIEMENTS = [\"Carte\", \"Carte\", \"Carte\", \"PayPal\", \"PayPal\", \"Virement\"]\n",
    "# Plus de ventes en fin d'année (soldes, Noël)\n",
    "POIDS_MOIS = [8, 6, 7, 7, 8, 8, 6, 5, 8, 9, 13, 15]\n",
    "\n",
    "\n",
    "def generer(dossier=\"data\", nb_ventes=5000, nb_clients=500):\n",
    "    rng = random.Random(42)\n",
    "    os.makedirs(dossier, exist_ok=True)\n",
    "\n",
    "    with open(os.path.join(dossier, \"texte.txt\"), \"w\", encoding=\"utf-8\") as f:\n",
    "        f.write(TEXTE)\n",
    "\n",
    "    with open(os.path.join(dossier, \"clients.csv\"), \"w\", newline=\"\", encoding=\"utf-8\") as f:\n",
    "        w = csv.writer(f)\n",
    "        w.writerow([\"id_client\", \"prenom\", \"age\", \"ville\", \"date_inscription\"])\n",
    "        for i in range(1, nb_clients + 1):\n",
    "            inscription = date(2022, 1, 1) + timedelta(days=rng.randint(0, 1400))\n",
    "            w.writerow([i, rng.choice(PRENOMS), rng.randint(18, 75), rng.choice(VILLES), inscription.isoformat()])\n",
    "\n",
    "    lignes = []\n",
    "    for i in range(1, nb_ventes + 1):\n",
    "        mois = rng.choices(range(1, 13), weights=POIDS_MOIS)[0]\n",
    "        jour = rng.randint(1, 28)\n",
    "        categorie = rng.choice(list(CATALOGUE))\n",
    "        produit, prix = rng.choice(CATALOGUE[categorie])\n",
    "        quantite = rng.choices([1, 2, 3, 4, 5], weights=[50, 25, 12, 8, 5])[0]\n",
    "        # ~1 % de ventes passées par des clients absents du fichier clients (pour les jointures)\n",
    "        id_client = rng.randint(501, 510) if rng.random() < 0.01 else rng.randint(1, nb_clients)\n",
    "        paiement = rng.choice(PAIEMENTS)\n",
    "        # Quelques valeurs manquantes, pour l'exercice de nettoyage\n",
    "        if rng.random() < 0.02:\n",
    "            quantite = \"\"\n",
    "        if rng.random() < 0.01:\n",
    "            paiement = \"\"\n",
    "        lignes.append([i, date(2025, mois, jour).isoformat(), id_client, produit, categorie, quantite, prix, paiement])\n",
    "    # Quelques doublons exacts, comme dans la vraie vie\n",
    "    lignes += [list(l) for l in rng.sample(lignes, 25)]\n",
    "    rng.shuffle(lignes)\n",
    "\n",
    "    with open(os.path.join(dossier, \"ventes.csv\"), \"w\", newline=\"\", encoding=\"utf-8\") as f:\n",
    "        w = csv.writer(f)\n",
    "        w.writerow([\"id_vente\", \"date_vente\", \"id_client\", \"produit\", \"categorie\", \"quantite\", \"prix_unitaire\", \"mode_paiement\"])\n",
    "        w.writerows(lignes)\n",
    "\n",
    "\n",
    "generer()\n",
    "print(sorted(os.listdir(\"data\")))"
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 0.5 — Créer la SparkSession"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 0.6 — Combien de cœurs ?"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 0.7 — Votre premier DataFrame"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 0.8 — Ouvrir la Spark UI"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Partie 1 — Les RDD\n",
    "\n",
    "La brique de base de Spark : map, filter, reduce et le célèbre word count."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.1 — C'est quoi un RDD ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.2 — Créer un RDD"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.3 — Une transformation : map"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.4 — Une action : collect"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.5 — Garder les nombres pairs"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.6 — La somme des carrés"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.7 — Lire un fichier texte"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.8 — Découper en mots : flatMap"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.9 — Former des paires (mot, 1)"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.10 — Additionner par mot : reduceByKey"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.11 — Un word count plus malin"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 1.12 — Les lignes qui parlent de Spark ★"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Partie 2 — Les DataFrames\n",
    "\n",
    "Charger un fichier CSV, l'explorer, filtrer et créer des colonnes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.1 — C'est quoi un DataFrame ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.2 — Charger les ventes"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.3 — Regarder les premières lignes"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.4 — Quelques statistiques"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.5 — Choisir des colonnes : select"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.6 — Filtrer des lignes : filter"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.7 — Combiner deux conditions"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.8 — Créer une colonne : withColumn"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.9 — Calculer le montant de chaque vente"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.10 — Trier : orderBy"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.11 — Les valeurs distinctes"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 2.12 — Petit, moyen ou gros panier ★"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Partie 3 — Nettoyer, regrouper, joindre\n",
    "\n",
    "Traiter les valeurs manquantes et les doublons, calculer des totaux, croiser deux tables."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.1 — Compter les valeurs manquantes"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.2 — Repérer les doublons"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.3 — Créer une table propre"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.4 — Garder la table en mémoire"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.5 — Regrouper : groupBy et agg"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.6 — Le chiffre d'affaires par catégorie"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.7 — Plusieurs calculs d'un coup"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.8 — Le chiffre d'affaires par mois"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.9 — Charger les clients"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.10 — Joindre deux tables : join"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.11 — Le chiffre d'affaires par ville"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.12 — Les ventes sans client"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 3.13 — Par tranche d'âge ★"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Partie 4 — Spark SQL\n",
    "\n",
    "Interroger ses données avec des requêtes SQL."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 4.1 — Déclarer des tables SQL"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 4.2 — Les produits les plus vendus"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 4.3 — Le CA par mode de paiement"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 4.4 — Une jointure en SQL"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 4.5 — SQL ou DataFrame ?"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Partie 5 — Mini-projet\n",
    "\n",
    "Le bilan de l'année pour la direction, puis la sauvegarde des résultats."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 5.1 — La mission"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 5.2 — CA total et nombre de ventes"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 5.3 — Panier moyen par mode de paiement"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 5.4 — Le produit phare de chaque catégorie ★"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 5.5 — Sauvegarder en Parquet"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 5.6 — Relire le fichier Parquet"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 5.7 — Un graphique du CA par mois ★"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Étape 5.8 — Arrêter Spark"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "source": [
    ""
   ],
   "execution_count": null,
   "outputs": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}