Commit
chore: chemins mis à jour après la réorganisation du Bureau (2026-11-01)
UQO/ est désormais structuré en Enseignement, Recherche, Administration, Supervision ; Recherche-UQO vit dans UQO/Recherche/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
modified
IMM1003-20_Elements_evaluation_immobiliere/03_Travaux_pratiques/TP1_Tableau_de_bord_du_marche/retroaction_etudiants/Bissonnette_David/courriel_Bissonnette_David.md
+2 −2
@@ -1,8 +1,8 @@ | ||
| 1 | 1 | **À :** David Bissonnette |
| 2 | 2 | **Objet :** IMM1003-20 — TP1 corrigé : votre note, la grille de correction et votre classeur annoté |
| 3 | 3 | **Pièces jointes :** |
| 4 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_BISD28318805_grille_correction.pdf` | |
| 5 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_BISD28318805_corrige_SPB.xlsx` | |
| 4 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_BISD28318805_grille_correction.pdf` | |
| 5 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_BISD28318805_corrige_SPB.xlsx` | |
| 6 | 6 | |
| 7 | 7 | Bonjour Monsieur Bissonnette, |
| 8 | 8 | |
modified
IMM1003-20_Elements_evaluation_immobiliere/03_Travaux_pratiques/TP1_Tableau_de_bord_du_marche/retroaction_etudiants/Richer_Caroline/courriel_Richer_Caroline.md
+2 −2
@@ -1,8 +1,8 @@ | ||
| 1 | 1 | **À :** Caroline Richer |
| 2 | 2 | **Objet :** IMM1003-20 — TP1 corrigé : votre note, la grille de correction et votre classeur annoté |
| 3 | 3 | **Pièces jointes :** |
| 4 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_RICC05286804_grille_correction.pdf` | |
| 5 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_RICC05286804_corrige_SPB.xlsx` | |
| 4 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_RICC05286804_grille_correction.pdf` | |
| 5 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_RICC05286804_corrige_SPB.xlsx` | |
| 6 | 6 | |
| 7 | 7 | Bonjour Madame Richer, |
| 8 | 8 | |
modified
IMM1003-20_Elements_evaluation_immobiliere/03_Travaux_pratiques/TP1_Tableau_de_bord_du_marche/retroaction_etudiants/Tessier-Proulx_Marieve/courriel_Tessier-Proulx_Marieve.md
+2 −2
@@ -1,8 +1,8 @@ | ||
| 1 | 1 | **À :** Mariève Tessier-Proulx |
| 2 | 2 | **Objet :** IMM1003-20 — TP1 corrigé : votre note, la grille de correction et votre classeur annoté |
| 3 | 3 | **Pièces jointes :** |
| 4 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_TESM78290105_grille_correction.pdf` | |
| 5 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_TESM78290105_corrige_SPB.xlsx` | |
| 4 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_TESM78290105_grille_correction.pdf` | |
| 5 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_TESM78290105_corrige_SPB.xlsx` | |
| 6 | 6 | |
| 7 | 7 | Bonjour Madame Tessier-Proulx, |
| 8 | 8 | |
added
IMM1003-20_Elements_evaluation_immobiliere/04_Examens/Intra_30pct/correction/_outils/make_correction_intra.py
+243 −0
@@ -0,0 +1,243 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +"""Grilles de correction de l'examen de mi-session IMM1003-20 (examen maison 72 h, 19 questions, 100 pts, 30 %). | |
| 3 | +Pour chaque copie du dict COPIES (fichier copies_intra_*.py) : une grille PDF (HTML + WeasyPrint), un courriel .md, | |
| 4 | +une ligne dans notes_intra_IMM1003.csv. | |
| 5 | +Usage : python3 -I make_correction_intra.py copies_intra_20261029 [CLE ...] | |
| 6 | +Sorties : INTRA_OUT (défaut ~/Desktop/UQO/Enseignement/corrections/examen-intra-imm1003/correction) + copie dans ../copies/. | |
| 7 | +""" | |
| 8 | +import html, os, sys, csv, subprocess, importlib, shutil | |
| 9 | + | |
| 10 | +HERE = os.path.dirname(os.path.abspath(__file__)) | |
| 11 | +sys.path.insert(0, HERE) | |
| 12 | +OUT = os.environ.get("INTRA_OUT", os.path.expanduser("~/Desktop/UQO/Enseignement/corrections/examen-intra-imm1003/correction")) | |
| 13 | +MIRROR = os.path.join(os.path.dirname(HERE), "copies") | |
| 14 | +os.makedirs(OUT, exist_ok=True); os.makedirs(MIRROR, exist_ok=True); os.makedirs("/tmp/corr", exist_ok=True) | |
| 15 | + | |
| 16 | +PARTIES = {"A": "Lire le marché de Gatineau dans nos ventes réelles", "B": "Valeur, prix, coût et rôle : le 505, rue Nadon", | |
| 17 | + "C": "Les municipalités voisines et les principes", "D": "Collecte, validation et sélection des données", | |
| 18 | + "E": "Cadre professionnel et déontologie"} | |
| 19 | +PARTIE_DE = {1: "A", 2: "A", 3: "A", 4: "A", 5: "B", 6: "B", 7: "B", 8: "B", 9: "C", 10: "C", 11: "C", | |
| 20 | + 12: "D", 13: "D", 14: "D", 15: "D", 16: "E", 17: "E", 18: "E", 19: "E"} | |
| 21 | +TITRES = {1: "Douze mois de marché", 2: "Moyenne, médiane et la maison du chemin d'Aylmer", 3: "La phase du cycle", | |
| 22 | + 4: "Gatineau face au Québec", 5: "Ce que la revente dit — et ne dit pas", 6: "Le rôle n'est pas le marché", | |
| 23 | + 7: "Lire une distribution de ratios", 8: "Prix, valeur, coût : quatre mandats", 9: "Chelsea coûte 59 % de plus", | |
| 24 | + 10: "Des ratios qu'on ne peut pas comparer", 11: "L'utilisation optimale d'une petite maison neuve", | |
| 25 | + 12: "Validation d'un extrait brut", 13: "Choisir ses comparables", 14: "Confirmer une vente", | |
| 26 | + 15: "Les limites de nos données", 16: "Le courtier ou l'évaluateur agréé ?", 17: "Le boni du chemin d'Aylmer", | |
| 27 | + 18: "Une erreur et ses conséquences", 19: "L'estimation n'est pas l'évaluation"} | |
| 28 | +MAX = {1: 6, 2: 5, 3: 5, 4: 6, 5: 5, 6: 5, 7: 5, 8: 5, 9: 6, 10: 5, 11: 7, 12: 8, 13: 8, 14: 4, 15: 4, 16: 4, 17: 4, 18: 4, 19: 4} | |
| 29 | + | |
| 30 | +# Valeurs de référence du solutionnaire (affichées dans la grille) | |
| 31 | +REF = [("Q1", "−25,2 % ventes · +5,3 % médiane · saisonnalité 2,14"), | |
| 32 | + ("Q2", "465 452 $ (écart ≈ 13 600 $) · vente légitime (ratio prix/rôle 0,96)"), | |
| 33 | + ("Q4", "Gatineau 122,2 / 128,0 / 132,2 · Québec 124,9 / 136,6 / 148,8"), | |
| 34 | + ("Q5", "+25,0 % → 4,6 %/an (TCAC) · marché +32,2 % → 5,7 %/an"), | |
| 35 | + ("Q6", "1,775 / 1,237 · références 1er juillet 2019 et 1er juillet 2022 (art. 46), valeur réelle (art. 43)"), | |
| 36 | + ("Q7", "1,237 entre P75 (1,180) et P90 (1,271) ≈ 85e centile"), | |
| 37 | + ("Q9", "1,585 / 1,154 / 8,7 · taille ≈ +37 % (1,585 ÷ 1,154)"), | |
| 38 | + ("Q13", "no 7 obligatoire · médiane ≈ 4 277 $/m² → ≈ 480 000 $ (470 000–520 000) · prix payé ≈ +20 %")] | |
| 39 | + | |
| 40 | + | |
| 41 | +def Q(num, items, note=None): | |
| 42 | + mx = sum(i[1] for i in items) | |
| 43 | + assert abs(mx - MAX[num]) < 1e-9, f"Q{num}: barème {mx} ≠ {MAX[num]}" | |
| 44 | + return {"num": num, "titre": TITRES[num], "items": items, "note": note, "max": mx, "got": sum(i[2] for i in items)} | |
| 45 | + | |
| 46 | + | |
| 47 | +def fr(x): | |
| 48 | + if abs(x - round(x)) < 1e-9: return str(int(round(x))) | |
| 49 | + return ("%.2f" % x).rstrip("0").rstrip(".").replace(".", ",") | |
| 50 | + | |
| 51 | + | |
| 52 | +def appreciation(n): | |
| 53 | + if n >= 90: return "Excellent" | |
| 54 | + if n >= 80: return "Très bien" | |
| 55 | + if n >= 70: return "Bien" | |
| 56 | + if n >= 60: return "Passable" | |
| 57 | + return "Insuffisant" | |
| 58 | + | |
| 59 | + | |
| 60 | +E = html.escape | |
| 61 | +CSS = """ | |
| 62 | +@page { size: A4; margin: 17mm 15mm 18mm 15mm; | |
| 63 | + @bottom-left { content: "UQO · IMM1003-20 · Examen de mi-session · Grille de correction"; font: 8pt 'Avenir Next', Helvetica; color:#58595B; } | |
| 64 | + @bottom-right { content: "Page " counter(page) " / " counter(pages); font: 8pt 'Avenir Next', Helvetica; color:#58595B; } } | |
| 65 | +* { box-sizing: border-box; } | |
| 66 | +body { font-family: 'Avenir Next', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 9.6pt; color: #1d1d1f; line-height: 1.38; } | |
| 67 | +h1,h2,h3 { margin: 0; } | |
| 68 | +.band { background: #003E7E; color: #fff; padding: 11px 16px; border-radius: 6px; display: flex; justify-content: space-between; align-items: center; } | |
| 69 | +.band .u { font-size: 8.5pt; letter-spacing: .04em; text-transform: uppercase; opacity: .85; } | |
| 70 | +.band h1 { font-size: 17pt; font-weight: 700; margin-top: 2px; } | |
| 71 | +.band .right { text-align: right; font-size: 9pt; line-height: 1.3; } | |
| 72 | +.ident { display: flex; gap: 12px; margin-top: 12px; } | |
| 73 | +.ident .box { flex: 1; background: #F0F4F8; border-radius: 6px; padding: 9px 12px; } | |
| 74 | +.ident .box table { width: 100%; border-collapse: collapse; } | |
| 75 | +.ident .box td { padding: 2px 0; vertical-align: top; } | |
| 76 | +.ident .box td.k { color: #58595B; width: 34%; font-size: 8.6pt; text-transform: uppercase; letter-spacing: .03em; } | |
| 77 | +.score { width: 190px; background: #008046; color: #fff; border-radius: 6px; padding: 10px 12px; text-align: center; display:flex; flex-direction:column; justify-content:center; } | |
| 78 | +.score.mid { background: #C6A300; } .score.low { background: #B42318; } | |
| 79 | +.score .big { font-size: 30pt; font-weight: 700; line-height: 1; } | |
| 80 | +.score .big small { font-size: 13pt; font-weight: 500; } | |
| 81 | +.score .app { font-size: 11pt; font-weight: 600; margin-top: 4px; } | |
| 82 | +.score .pond { font-size: 8.5pt; opacity: .9; margin-top: 3px; } | |
| 83 | +h2 { font-size: 12.5pt; color: #003E7E; border-bottom: 2px solid #0066B3; padding-bottom: 3px; margin: 16px 0 8px; } | |
| 84 | +h2 .pts { float: right; font-size: 10.5pt; color: #fff; background: #0066B3; border-radius: 10px; padding: 1px 10px; font-weight: 600; } | |
| 85 | +h2 .pts.low { background: #B42318; } h2 .pts.mid { background: #C6A300; } h2 .pts.full { background: #008046; } | |
| 86 | +h3.partie { font-size: 10.5pt; color: #fff; background: #0066B3; padding: 4px 10px; border-radius: 4px; margin: 14px 0 4px; } | |
| 87 | +table.grid { width: 100%; border-collapse: collapse; margin-top: 4px; } | |
| 88 | +table.grid th { background: #003E7E; color: #fff; font-weight: 600; font-size: 8.6pt; text-align: left; padding: 5px 7px; } | |
| 89 | +table.grid td { padding: 5px 7px; border-bottom: 1px solid #d9e1ea; vertical-align: top; } | |
| 90 | +table.grid tr:nth-child(even) td { background: #F7F9FC; } | |
| 91 | +table.grid td.num { text-align: center; white-space: nowrap; font-variant-numeric: tabular-nums; } | |
| 92 | +table.grid td.c { font-weight: 600; } | |
| 93 | +table.grid tr.part td { background: #E8EEF5; font-weight: 600; color: #003E7E; } | |
| 94 | +.bar { height: 7px; background: #e3e8ee; border-radius: 4px; overflow: hidden; width: 110px; display: inline-block; vertical-align: middle; margin-left: 6px; } | |
| 95 | +.bar i { display: block; height: 100%; background: #008046; } | |
| 96 | +.bar i.mid { background: #C6A300; } .bar i.low { background: #B42318; } | |
| 97 | +.note { background: #FFF6D5; border-left: 4px solid #C6A300; padding: 6px 10px; margin: 6px 0 2px; font-size: 9.2pt; border-radius: 0 4px 4px 0; } | |
| 98 | +.two { display: flex; gap: 12px; } | |
| 99 | +.two > div { flex: 1; } | |
| 100 | +.card { border: 1px solid #d9e1ea; border-radius: 6px; padding: 8px 12px; } | |
| 101 | +.card h3 { font-size: 10.5pt; margin-bottom: 4px; } | |
| 102 | +.card.plus h3 { color: #008046; } .card.minus h3 { color: #B42318; } | |
| 103 | +ul { margin: 2px 0 2px 16px; padding: 0; } li { margin: 2px 0; } | |
| 104 | +.resume { background: #F0F4F8; border-radius: 6px; padding: 9px 12px; margin-top: 10px; font-size: 9.8pt; } | |
| 105 | +.legend { font-size: 8.6pt; color: #58595B; margin-top: 6px; } | |
| 106 | +.small { font-size: 8.6pt; color: #58595B; } | |
| 107 | +.pb { page-break-before: always; } | |
| 108 | +.sig { margin-top: 18px; display:flex; justify-content: space-between; font-size: 9pt; color:#58595B; } | |
| 109 | +""" | |
| 110 | + | |
| 111 | + | |
| 112 | +def build_html(S, total): | |
| 113 | + tot_max = sum(q["max"] for q in S["questions"]) | |
| 114 | + assert tot_max == 100 | |
| 115 | + def cls(got, mx): | |
| 116 | + r = got / mx if mx else 1 | |
| 117 | + return "full" if r >= 0.999 else ("low" if r < 0.6 else "mid") | |
| 118 | + def barcls(got, mx): | |
| 119 | + r = got / mx if mx else 1 | |
| 120 | + return "" if r >= 0.85 else ("low" if r < 0.6 else "mid") | |
| 121 | + scorecls = "" if total >= 80 else ("low" if total < 60 else "mid") | |
| 122 | + h = [f"<html><head><meta charset='utf-8'><style>{CSS}</style></head><body>"] | |
| 123 | + h.append(f""" | |
| 124 | +<div class="band"> | |
| 125 | + <div><div class="u">Université du Québec en Outaouais · Département des sciences administratives</div> | |
| 126 | + <h1>Grille de correction — Examen de mi-session</h1> | |
| 127 | + <div class="u">IMM1003-20 · Éléments d'évaluation immobilière · Automne 2026</div></div> | |
| 128 | + <div class="right">Examen maison individuel (72 h)<br>19 questions · 5 parties · 100 points · 30 % de la note finale<br>Données réelles d'UQO Éval (6 octobre 2026)</div> | |
| 129 | +</div> | |
| 130 | +<div class="ident"> | |
| 131 | + <div class="box"><table> | |
| 132 | + <tr><td class="k">Étudiant·e</td><td><b>{E(S['nom'])}</b></td></tr> | |
| 133 | + <tr><td class="k">Code permanent</td><td>{S['code']}</td></tr> | |
| 134 | + <tr><td class="k">Remise</td><td>{E(S['remise'])}</td></tr> | |
| 135 | + <tr><td class="k">Déclarations</td><td>{E(S['declarations'])}</td></tr> | |
| 136 | + <tr><td class="k">Correction</td><td>Simon-Pierre Boucher · {E(S['date_corr'])}</td></tr> | |
| 137 | + </table></div> | |
| 138 | + <div class="score {scorecls}"><div class="big">{fr(total)}<small> / 100</small></div> | |
| 139 | + <div class="app">{appreciation(total)}</div> | |
| 140 | + <div class="pond">Pondération 30 % → {fr(round(total * 0.3, 2))} / 30</div></div> | |
| 141 | +</div> | |
| 142 | +<div class="resume"><b>En résumé.</b> {E(S['resume'])}</div> | |
| 143 | +""") | |
| 144 | + h.append("<h2>Récapitulatif par question</h2><table class='grid'><tr><th style='width:34px'>Q</th><th>Objet évalué</th><th style='width:56px'>Points</th><th style='width:56px'>Obtenu</th><th style='width:150px'>Progression</th></tr>") | |
| 145 | + cur = None | |
| 146 | + for q in S["questions"]: | |
| 147 | + p = PARTIE_DE[q["num"]] | |
| 148 | + if p != cur: | |
| 149 | + cur = p | |
| 150 | + pm = sum(x["max"] for x in S["questions"] if PARTIE_DE[x["num"]] == p) | |
| 151 | + pg = sum(x["got"] for x in S["questions"] if PARTIE_DE[x["num"]] == p) | |
| 152 | + h.append(f"<tr class='part'><td class='num'>{p}</td><td>{E(PARTIES[p])}</td><td class='num'>{fr(pm)}</td><td class='num'>{fr(pg)}</td><td class='num'>{fr(round(pg/pm*100))} %</td></tr>") | |
| 153 | + r = q["got"] / q["max"] | |
| 154 | + h.append(f"<tr><td class='num c'>{q['num']}</td><td>{E(q['titre'])}</td><td class='num'>{fr(q['max'])}</td><td class='num c'>{fr(q['got'])}</td>" | |
| 155 | + f"<td class='num'>{fr(round(r*100))} %<span class='bar'><i class='{barcls(q['got'],q['max'])}' style='width:{r*100:.0f}%'></i></span></td></tr>") | |
| 156 | + h.append(f"<tr><td></td><td class='c'>Total</td><td class='num c'>100</td><td class='num c'>{fr(total)}</td><td class='num c'>{fr(round(total))} %</td></tr></table>") | |
| 157 | + h.append("<div class='two' style='margin-top:12px'><div class='card plus'><h3>Points forts</h3><ul>" + "".join(f"<li>{E(x)}</li>" for x in S["forces"]) + | |
| 158 | + "</ul></div><div class='card minus'><h3>À améliorer</h3><ul>" + "".join(f"<li>{E(x)}</li>" for x in S["ameliorer"]) + "</ul></div></div>") | |
| 159 | + h.append("<h2>Vos résultats et la solution de référence</h2><table class='grid'><tr><th style='width:40px'>Q</th><th>Votre copie</th><th>Référence du cours (solutionnaire)</th></tr>") | |
| 160 | + for (k, ref), mine in zip(REF, S["cles"]): | |
| 161 | + h.append(f"<tr><td class='c'>{k}</td><td>{E(mine)}</td><td>{E(ref)}</td></tr>") | |
| 162 | + h.append("</table>") | |
| 163 | + h.append("<h2 style='margin-top:18px'>Détail de la correction, question par question</h2>") | |
| 164 | + cur = None | |
| 165 | + for q in S["questions"]: | |
| 166 | + p = PARTIE_DE[q["num"]] | |
| 167 | + if p != cur: | |
| 168 | + cur = p | |
| 169 | + h.append(f"<h3 class='partie'>Partie {p} — {E(PARTIES[p])}</h3>") | |
| 170 | + h.append(f"<h2>Question {q['num']} — {E(q['titre'])}<span class='pts {cls(q['got'],q['max'])}'>{fr(q['got'])} / {fr(q['max'])}</span></h2>") | |
| 171 | + h.append("<table class='grid'><tr><th style='width:30%'>Critère (grille de correction)</th><th style='width:40px'>Max</th><th style='width:52px'>Obtenu</th><th>Observations</th></tr>") | |
| 172 | + for lab, mx, got, com in q["items"]: | |
| 173 | + h.append(f"<tr><td>{E(lab)}</td><td class='num'>{fr(mx)}</td><td class='num c'>{fr(got)}</td><td>{E(com)}</td></tr>") | |
| 174 | + h.append("</table>") | |
| 175 | + if q["note"]: | |
| 176 | + h.append(f"<div class='note'>{E(q['note'])}</div>") | |
| 177 | + h.append("<div class='legend' style='margin-top:12px'><b>Règles appliquées</b> (grille de correction de l'examen) : démarche = formule + substitution + résultat + interprétation ; " | |
| 178 | + "erreur d'arithmétique isolée : −0,5 au plus ; erreur reportée pénalisée une seule fois ; interprétation sans donnée précise du questionnaire : au plus 50 % de l'item ; " | |
| 179 | + "tolérances ±0,1 point de % sur les variations, ±0,003 sur les ratios, ±50 $ sur les montants ; l'usage déclaré d'outils d'IA n'est jamais pénalisé en soi ; " | |
| 180 | + "le format de la remise (PDF, classeur Excel, copie manuscrite) n'est pas noté.</div>") | |
| 181 | + h.append(f"<div class='sig'><span>Note finale : <b>{fr(total)} / 100</b> ({appreciation(total)}) — saisie Moodle : {fr(round(total * 0.3, 2))} / 30</span><span>Simon-Pierre Boucher, professeur · {E(S['date_corr'])}</span></div>") | |
| 182 | + h.append("</body></html>") | |
| 183 | + return "\n".join(h) | |
| 184 | + | |
| 185 | + | |
| 186 | +def courriel(S, total): | |
| 187 | + prenom = S["nom"].split(",")[1].strip() if "," in S["nom"] else S["nom"].split()[0] | |
| 188 | + lines = [f"**À :** {S.get('courriel', '')} ", f"**Objet :** IMM1003-20 — Examen de mi-session : votre note et la grille de correction", "", | |
| 189 | + f"Bonjour {prenom},", "", | |
| 190 | + f"Votre examen de mi-session est corrigé : **{fr(total)} / 100** ({appreciation(total)}), soit {fr(round(total*0.3,2))} / 30 dans la note finale. " | |
| 191 | + f"La grille de correction détaillée est en pièce jointe ({S['out_base']}_grille_correction.pdf) : barème par question, points obtenus et observations.", "", | |
| 192 | + "**En résumé.** " + S["resume"], "", "**Points forts**", ""] + [f"- {x}" for x in S["forces"]] + ["", "**À travailler pour l'examen final**", ""] + [f"- {x}" for x in S["ameliorer"]] | |
| 193 | + if S.get("courriel_note"): | |
| 194 | + lines += ["", S["courriel_note"]] | |
| 195 | + lines += ["", "N'hésitez pas à passer me voir ou à m'écrire pour revoir une question avec vous.", "", "Simon-Pierre Boucher ", "Professeur, IMM1003-20"] | |
| 196 | + return "\n".join(lines) | |
| 197 | + | |
| 198 | + | |
| 199 | +def run(S): | |
| 200 | + total = round(sum(q["got"] for q in S["questions"]) * 4) / 4 | |
| 201 | + S["total"] = total | |
| 202 | + base = S["out_base"] | |
| 203 | + html_path = f"/tmp/corr/{base}_grille.html" | |
| 204 | + pdf_path = os.path.join(OUT, f"{base}_grille_correction.pdf") | |
| 205 | + open(html_path, "w", encoding="utf-8").write(build_html(S, total)) | |
| 206 | + subprocess.run(["/opt/homebrew/bin/weasyprint", html_path, pdf_path], check=True) | |
| 207 | + slug = S["nom"].replace(", ", "_").replace(" ", "_").replace("'", "") | |
| 208 | + md_path = os.path.join(OUT, f"courriel_{slug}.md") | |
| 209 | + open(md_path, "w", encoding="utf-8").write(courriel(S, total)) | |
| 210 | + for p in (pdf_path, md_path): | |
| 211 | + shutil.copy2(p, MIRROR) | |
| 212 | + print(f"{S['nom']} ({S['code']}) : {fr(total)} / 100 → {pdf_path}") | |
| 213 | + return total | |
| 214 | + | |
| 215 | + | |
| 216 | +def registry(rows): | |
| 217 | + path = os.path.join(OUT, "notes_intra_IMM1003.csv") | |
| 218 | + existing = {} | |
| 219 | + if os.path.exists(path): | |
| 220 | + with open(path, encoding="utf-8") as f: | |
| 221 | + for r in csv.DictReader(f): | |
| 222 | + existing[r["code"]] = r | |
| 223 | + for r in rows: | |
| 224 | + existing[r["code"]] = r | |
| 225 | + cols = ["code", "nom", "note_100", "note_30", "appreciation", "date_correction", "remise", "declarations"] | |
| 226 | + with open(path, "w", encoding="utf-8", newline="") as f: | |
| 227 | + w = csv.DictWriter(f, fieldnames=cols); w.writeheader() | |
| 228 | + for k in sorted(existing, key=lambda c: existing[c]["nom"]): | |
| 229 | + w.writerow({c: existing[k].get(c, "") for c in cols}) | |
| 230 | + shutil.copy2(path, MIRROR) | |
| 231 | + print("registre :", path) | |
| 232 | + | |
| 233 | + | |
| 234 | +if __name__ == "__main__": | |
| 235 | + mod = importlib.import_module(sys.argv[1]) | |
| 236 | + keys = sys.argv[2:] or list(mod.COPIES) | |
| 237 | + rows = [] | |
| 238 | + for k in keys: | |
| 239 | + S = mod.COPIES[k] | |
| 240 | + t = run(S) | |
| 241 | + rows.append({"code": S["code"], "nom": S["nom"], "note_100": fr(t), "note_30": fr(round(t * 0.3, 2)), "appreciation": appreciation(t), | |
| 242 | + "date_correction": S["date_corr"], "remise": S["remise"], "declarations": S["declarations"]}) | |
| 243 | + registry(rows) | |
modified
IMM1003-20_Elements_evaluation_immobiliere/05_Ressources/scripts_gabarits_excel/make_IMM1003_TP2.py
+1 −1
@@ -26,7 +26,7 @@ from uqo_xlsx import ( | ||
| 26 | 26 | table, titre_section, zone_texte, |
| 27 | 27 | ) |
| 28 | 28 | |
| 29 | −OUT = "/Users/simon-pierreboucher/Desktop/UQO/UQO_PREPARATION_COURS/output/IMM1003-20/TP2/IMM1003-20_TP2_gabarit.xlsx" | |
| 29 | +OUT = "/Users/simon-pierreboucher/Desktop/UQO/Enseignement/UQO_PREPARATION_COURS/output/IMM1003-20/TP2/IMM1003-20_TP2_gabarit.xlsx" | |
| 30 | 30 | FMT_D = '#,##0" $"' |
| 31 | 31 | FMT_P1 = "0.0%" |
| 32 | 32 | |
modified
IMM1003-20_Elements_evaluation_immobiliere/05_Ressources/scripts_gabarits_excel/make_IMM1003_TP3.py
+1 −1
@@ -26,7 +26,7 @@ from uqo_xlsx import ( | ||
| 26 | 26 | table, titre_section, zone_texte, |
| 27 | 27 | ) |
| 28 | 28 | |
| 29 | −OUT = "/Users/simon-pierreboucher/Desktop/UQO/UQO_PREPARATION_COURS/output/IMM1003-20/TP3/IMM1003-20_TP3_gabarit.xlsx" | |
| 29 | +OUT = "/Users/simon-pierreboucher/Desktop/UQO/Enseignement/UQO_PREPARATION_COURS/output/IMM1003-20/TP3/IMM1003-20_TP3_gabarit.xlsx" | |
| 30 | 30 | FMT_D = '#,##0" $"' |
| 31 | 31 | FMT_P1 = "0.0%" |
| 32 | 32 | FMT_P2 = "0.00%" |
modified
IMM1033-20_Methodes_du_cout/03_Travaux_pratiques/TP1_Evaluation_d_un_terrain/retroaction_etudiants/Aubut_Bergeron_Olivier/courriel_Aubut_Bergeron_Olivier.md
+2 −2
@@ -1,8 +1,8 @@ | ||
| 1 | 1 | **À :** Olivier Aubut Bergeron |
| 2 | 2 | **Objet :** IMM1033-20 — TP1 corrigé : votre note, la grille de correction et votre classeur annoté |
| 3 | 3 | **Pièces jointes :** |
| 4 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_AUBO04088109_grille_correction.pdf` | |
| 5 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_AUBO04088109_corrige_SPB.xlsx` | |
| 4 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_AUBO04088109_grille_correction.pdf` | |
| 5 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_AUBO04088109_corrige_SPB.xlsx` | |
| 6 | 6 | |
| 7 | 7 | Bonjour Monsieur Aubut Bergeron, |
| 8 | 8 | |
modified
IMM1033-20_Methodes_du_cout/03_Travaux_pratiques/TP1_Evaluation_d_un_terrain/retroaction_etudiants/Besbes_Sarah/courriel_Besbes_Sarah.md
+2 −2
@@ -1,8 +1,8 @@ | ||
| 1 | 1 | **À :** Sarah Besbes |
| 2 | 2 | **Objet :** IMM1033-20 — TP1 corrigé : 85 / 100, grille de correction et classeur annoté |
| 3 | 3 | **Pièces jointes :** |
| 4 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_BESS76660205_grille_correction.pdf` | |
| 5 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_BESS76660205_corrige_SPB.xlsx` | |
| 4 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_BESS76660205_grille_correction.pdf` | |
| 5 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_BESS76660205_corrige_SPB.xlsx` | |
| 6 | 6 | |
| 7 | 7 | Bonjour Madame Besbes, |
| 8 | 8 | |
modified
IMM1033-20_Methodes_du_cout/03_Travaux_pratiques/TP1_Evaluation_d_un_terrain/retroaction_etudiants/Couture_Nancy/courriel_Couture_Nancy.md
+2 −2
@@ -1,8 +1,8 @@ | ||
| 1 | 1 | **À :** Nancy Couture |
| 2 | 2 | **Objet :** IMM1033-20 — TP1 corrigé : votre note, la grille de correction et votre classeur annoté |
| 3 | 3 | **Pièces jointes :** |
| 4 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_COUN25268204_grille_correction.pdf` | |
| 5 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_COUN25268204_corrige_SPB.xlsx` | |
| 4 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_COUN25268204_grille_correction.pdf` | |
| 5 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_COUN25268204_corrige_SPB.xlsx` | |
| 6 | 6 | |
| 7 | 7 | Bonjour Madame Couture, |
| 8 | 8 | |
modified
IMM1033-20_Methodes_du_cout/03_Travaux_pratiques/TP1_Evaluation_d_un_terrain/retroaction_etudiants/Gravel_Stella/courriel_Gravel_Stella.md
+2 −2
@@ -1,8 +1,8 @@ | ||
| 1 | 1 | **À :** Stella Gravel |
| 2 | 2 | **Objet :** IMM1033-20 — TP1 corrigé : votre note, la grille de correction et votre classeur annoté |
| 3 | 3 | **Pièces jointes :** |
| 4 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_GRAS76330300_grille_correction.pdf` | |
| 5 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_GRAS76330300_corrige_SPB.xlsx` | |
| 4 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_GRAS76330300_grille_correction.pdf` | |
| 5 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_GRAS76330300_corrige_SPB.xlsx` | |
| 6 | 6 | |
| 7 | 7 | Bonjour Madame Gravel, |
| 8 | 8 | |
modified
IMM1033-20_Methodes_du_cout/03_Travaux_pratiques/TP1_Evaluation_d_un_terrain/retroaction_etudiants/Jacques_Michele/courriel_Jacques_Michele.md
+2 −2
@@ -1,8 +1,8 @@ | ||
| 1 | 1 | **À :** Michèle Jacques |
| 2 | 2 | **Objet :** IMM1033-20 — TP1 corrigé : votre note, la grille de correction et votre classeur annoté |
| 3 | 3 | **Pièces jointes :** |
| 4 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_JACM20518300_grille_correction.pdf` | |
| 5 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_JACM20518300_corrige_SPB.xlsx` | |
| 4 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_JACM20518300_grille_correction.pdf` | |
| 5 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_JACM20518300_corrige_SPB.xlsx` | |
| 6 | 6 | |
| 7 | 7 | Bonjour Madame Jacques, |
| 8 | 8 | |
modified
IMM1033-20_Methodes_du_cout/03_Travaux_pratiques/TP1_Evaluation_d_un_terrain/retroaction_etudiants/PrudHomme_Erika/courriel_PrudHomme_Erika.md
+2 −2
@@ -1,8 +1,8 @@ | ||
| 1 | 1 | **À :** Erika Prud'Homme |
| 2 | 2 | **Objet :** IMM1033-20 — TP1 corrigé : votre note, la grille de correction et votre classeur annoté |
| 3 | 3 | **Pièces jointes :** |
| 4 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_PRUE06519808_grille_correction.pdf` | |
| 5 | −- `/Users/simon-pierreboucher/Desktop/tp-à-corrigé/correction/TP1_PRUE06519808_corrige_SPB.xlsx` | |
| 4 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_PRUE06519808_grille_correction.pdf` | |
| 5 | +- `/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé/correction/TP1_PRUE06519808_corrige_SPB.xlsx` | |
| 6 | 6 | |
| 7 | 7 | Bonjour Madame Prud'Homme, |
| 8 | 8 | |
modified
IMM1033-20_Methodes_du_cout/03_Travaux_pratiques/TP1_Evaluation_d_un_terrain/retroaction_etudiants/_outils/make_correction_TP1.py
+1 −1
@@ -6,7 +6,7 @@ | ||
| 6 | 6 | """ |
| 7 | 7 | import html, math, os, subprocess, csv, sys, shutil |
| 8 | 8 | |
| 9 | −SRC = "/Users/simon-pierreboucher/Desktop/tp-à-corrigé" | |
| 9 | +SRC = "/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé" | |
| 10 | 10 | OUT = os.path.join(SRC, "correction") |
| 11 | 11 | os.makedirs(OUT, exist_ok=True) |
| 12 | 12 | DATE_CORR = "10 octobre 2026" |
modified
IMM1033-20_Methodes_du_cout/03_Travaux_pratiques/TP1_Evaluation_d_un_terrain/retroaction_etudiants/_outils/make_correction_TP1_v2.py
+1 −1
@@ -10,7 +10,7 @@ import html, math, os, subprocess, csv, sys, importlib | ||
| 10 | 10 | |
| 11 | 11 | HERE = os.path.dirname(os.path.abspath(__file__)) |
| 12 | 12 | sys.path.insert(0, HERE) |
| 13 | −SRC = os.environ.get("TP_SRC", "/Users/simon-pierreboucher/Desktop/tp-à-corrigé") # TP_SRC/TP_OUT : dossier de travail hors Bureau (Terminal sans accès TCC, 2026-10-27) | |
| 13 | +SRC = os.environ.get("TP_SRC", "/Users/simon-pierreboucher/Desktop/UQO/Enseignement/corrections/tp-à-corrigé") # TP_SRC/TP_OUT : dossier de travail hors Bureau (Terminal sans accès TCC, 2026-10-27) | |
| 14 | 14 | OUT = os.environ.get("TP_OUT", os.path.join(SRC, "correction")) |
| 15 | 15 | os.makedirs(OUT, exist_ok=True); os.makedirs("/tmp/corr", exist_ok=True) |
| 16 | 16 | DATE_CORR = "20 octobre 2026" |
modified
IMM1033-20_Methodes_du_cout/05_Ressources/scripts_gabarits_excel/make_IMM1033_TP3.py
+1 −1
@@ -25,7 +25,7 @@ from uqo_xlsx import ( | ||
| 25 | 25 | table, titre_section, zone_texte, |
| 26 | 26 | ) |
| 27 | 27 | |
| 28 | −OUT = "/Users/simon-pierreboucher/Desktop/UQO/UQO_PREPARATION_COURS/output/IMM1033-20/TP3/IMM1033-20_TP3_gabarit.xlsx" | |
| 28 | +OUT = "/Users/simon-pierreboucher/Desktop/UQO/Enseignement/UQO_PREPARATION_COURS/output/IMM1033-20/TP3/IMM1033-20_TP3_gabarit.xlsx" | |
| 29 | 29 | FMT_D = '#,##0" $"' |
| 30 | 30 | FMT_PSF = '0.00" $/pi²"' |
| 31 | 31 | FMT_P1 = "0.0%" |
modified
_Suivi_prof/Calendrier_prof_IMM1003_A26.html
+1 −1
@@ -6,7 +6,7 @@ | ||
| 6 | 6 | body { font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif; color: #111827; font-size: 9pt; margin: 0; } |
| 7 | 7 | .page { page-break-after: always; position: relative; } |
| 8 | 8 | .page:last-child { page-break-after: auto; } |
| 9 | −.page::before { content: ""; display: block; height: 12mm; background: url("file:///Users/simon-pierreboucher/Desktop/UQO/UQO_COURS/_Suivi_prof/uqo-logo.png") no-repeat left center; background-size: auto 10.5mm; border-bottom: 2px solid #005f85; margin-bottom: 3.5mm; } | |
| 9 | +.page::before { content: ""; display: block; height: 12mm; background: url("file:///Users/simon-pierreboucher/Desktop/UQO/Enseignement/UQO_COURS/_Suivi_prof/uqo-logo.png") no-repeat left center; background-size: auto 10.5mm; border-bottom: 2px solid #005f85; margin-bottom: 3.5mm; } | |
| 10 | 10 | .page::after { content: "IMM1003 · Automne 2026"; position: absolute; top: 0; right: 0; height: 12mm; line-height: 12mm; font-weight: 600; color: #005f85; font-size: 11pt; letter-spacing: .02em; } |
| 11 | 11 | h1 { font-size: 20pt; font-weight: 700; margin: 0 0 3mm; color: #0f172a; letter-spacing: -.01em; } |
| 12 | 12 | h1 .thin { font-weight: 400; color: #334155; } |
modified
_Suivi_prof/Calendrier_prof_IMM1033_A26.html
+1 −1
@@ -6,7 +6,7 @@ | ||
| 6 | 6 | body { font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif; color: #111827; font-size: 9pt; margin: 0; } |
| 7 | 7 | .page { page-break-after: always; position: relative; } |
| 8 | 8 | .page:last-child { page-break-after: auto; } |
| 9 | −.page::before { content: ""; display: block; height: 12mm; background: url("file:///Users/simon-pierreboucher/Desktop/UQO/UQO_COURS/_Suivi_prof/uqo-logo.png") no-repeat left center; background-size: auto 10.5mm; border-bottom: 2px solid #005f85; margin-bottom: 3.5mm; } | |
| 9 | +.page::before { content: ""; display: block; height: 12mm; background: url("file:///Users/simon-pierreboucher/Desktop/UQO/Enseignement/UQO_COURS/_Suivi_prof/uqo-logo.png") no-repeat left center; background-size: auto 10.5mm; border-bottom: 2px solid #005f85; margin-bottom: 3.5mm; } | |
| 10 | 10 | .page::after { content: "IMM1033 · Automne 2026"; position: absolute; top: 0; right: 0; height: 12mm; line-height: 12mm; font-weight: 600; color: #005f85; font-size: 11pt; letter-spacing: .02em; } |
| 11 | 11 | h1 { font-size: 20pt; font-weight: 700; margin: 0 0 3mm; color: #0f172a; letter-spacing: -.01em; } |
| 12 | 12 | h1 .thin { font-weight: 400; color: #334155; } |
| 13 | 13 | |