Python 64.6%
TypeScript 33.7%
CSS 0.8%
1import io23import pytest4from openpyxl import load_workbook56from app.tools import excel_ops7from app.tools.appraisal_calc import FUNCS8from app.tools.create_docx import build as build_docx9from app.tools.create_excel import build_workbook10from app.tools.excel_templates import TEMPLATES11from app.tools.make_chart import render12from app.tools.unit_convert import convert131415def _workbook() -> bytes:16 return build_workbook(TEMPLATES["comparables_ajustes"]({}))[0]171819def test_inspect_and_preview() -> None:20 data = _workbook()21 info = excel_ops.inspect(data)22 assert any(s["name"] == "Comparables" for s in info["sheets"])23 assert any("=" in line for s in info["sheets"] for line in s["cells"])24 pv = excel_ops.preview(data)25 assert pv["sheet"] == "Comparables" and len(pv["all"]) >= 2262728def test_edit_operations_add_column_row_sheet_chart() -> None:29 data = _workbook()30 hr = excel_ops._header_row(load_workbook(io.BytesIO(data))["Comparables"])31 new, log = excel_ops.apply_operations(data, [32 {"op": "add_column", "sheet": "Comparables", "header": "Piscine ($)", "formula": "=B{r}*0.02", "format": "currency"},33 {"op": "set_cell", "sheet": "Comparables", "cell": "B4", "value": "1 250 pi²", "input": True},34 {"op": "add_sheet", "name": "Synthèse", "title": "Synthèse", "table": {"columns": ["Poste", "Montant ($)"],35 "rows": [["Terrain", "120 000 $"], ["Bâtiment", 300000]],36 "totals": {"label": "Total", "formula": "=B5+B6"}}},37 {"op": "add_chart", "sheet": "Synthèse", "type": "pie", "categories_range": "A5:A6", "values_range": "B5:B6", "anchor": "E4"},38 {"op": "add_note", "sheet": "Comparables", "cell": "A1", "text": "Grille révisée"},39 {"op": "format", "sheet": "Comparables", "range": "B4:B4", "format": "area", "bold": True},40 ])41 wb = load_workbook(io.BytesIO(new))42 ws = wb["Comparables"]43 headers = [c.value for c in ws[hr]]44 assert "Piscine ($)" in headers45 col = headers.index("Piscine ($)") + 146 assert ws.cell(row=hr + 1, column=col).value == f"=B{hr + 1}*0.02"47 assert ws["B4"].value == 1250 and ws["A1"].comment is not None48 assert "Synthèse" in wb.sheetnames and wb["Synthèse"]["B7"].value == "=B5+B6"49 assert len(log) == 6505152def test_edit_unknown_op_raises() -> None:53 with pytest.raises(excel_ops.ExcelOpError):54 excel_ops.apply_operations(_workbook(), [{"op": "explode"}])555657def test_appraisal_functions() -> None:58 ca = FUNCS["cost_approach"]({"land_value": "120 000 $", "cost_new": 322000, "effective_age": 15, "economic_life": 60})59 assert round(ca["value"]) == 36150060 bd = FUNCS["breakdown_depreciation"]({61 "cost_new": 500000,62 "curable_physical": [{"item": "peinture", "cost_to_cure": 8000}],63 "short_lived": [{"item": "toiture", "cost_new": 30000, "effective_age": 10, "life": 20}],64 "long_lived": {"effective_age": 15, "economic_life": 60},65 "functional": [{"item": "cuisine", "type": "déficience", "cost_to_cure": 25000, "cost_if_new": 15000}],66 "external": {"rent_loss_annual": 2400, "cap_rate": "8 %", "building_share": 0.8},67 })68 assert bd["components"]["curable_physical"] == 8000 and bd["components"]["short_lived"] == 1500069 assert round(bd["components"]["long_lived"]) == round((500000 - 8000 - 30000) * 15 / 60)70 assert bd["components"]["functional"] == 10000 and round(bd["components"]["external"]) == 2400071 grid = FUNCS["adjust_comparables"]({72 "subject": {"superficie": 1200, "garage": "Oui", "etat": "bon"},73 "rates": {"superficie": 120, "garage": 15000, "etat": 10000},74 "monthly_trend": "0,5 %",75 "comparables": [{"address": "A", "price": 425000, "months": 6, "superficie": 1150, "garage": "Oui", "etat": "bon"},76 {"address": "B", "price": 398000, "months": 8, "superficie": 1250, "garage": "Non", "etat": "moyen"}],77 })78 a = grid["comparables"][0]79 assert round(a["time_adjusted"]) == round(425000 * 1.03) and a["adjustments"]["superficie"] == 600080 b = grid["comparables"][1]81 assert b["adjustments"]["garage"] == 15000 and b["adjustments"]["etat"] == 1000082 assert grid["stats"]["least_adjusted"] == "A"83 dc = FUNCS["direct_capitalization"]({"potential_gross_income": 100000, "vacancy_pct": 5, "operating_expenses": 40000, "cap_rate": 7})84 assert round(dc["value"]) == round(55000 / 0.07)858687def test_unit_convert() -> None:88 assert abs(convert(100, "m2", "pi2")["result"] - 1076.39) < 0.0189 assert abs(convert(2450, "$/m2", "$/pi2")["result"] - 227.61) < 0.0190 assert abs(convert(1, "arpent", "m2")["result"] - 3418.894) < 0.0191 with pytest.raises(ValueError):92 convert(1, "m2", "pi")939495def test_make_chart_and_docx() -> None:96 png = render({"type": "bar", "title": "Prix ajustés", "x": ["A", "B", "C"],97 "series": [{"name": "Prix", "values": [425000, "398 000 $", 449000]}], "value_format": "currency",98 "annotate": True})99 assert png[:8] == b"\x89PNG\r\n\x1a\n" and len(png) > 5000100 docx = build_docx({"title": "Fiche — Dépréciation", "course": "IMM1033", "subtitle": "",101 "markdown": "# Définitions\n\nLa **dépréciation** est $D = \\frac{A_e}{DVE} \\times C_N$.\n\n"102 "- physique\n- fonctionnelle\n\n| Type | Exemple |\n|---|---|\n| Physique | toiture |\n\n"103 "1. étape un\n2. étape deux\n\n> Rappel : le terrain ne se déprécie pas."})104 assert docx[:2] == b"PK" and len(docx) > 10000105