[{"data":1,"prerenderedAt":2574},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Faudit-every-formula-in-a-workbook-with-python":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Faudit-every-formula-in-a-workbook-with-python":2540,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Faudit-every-formula-in-a-workbook-with-python":2548,"children:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Faudit-every-formula-in-a-workbook-with-python":2553,"related:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Faudit-every-formula-in-a-workbook-with-python":2554},{"id":4,"title":5,"body":6,"dateModified":2505,"datePublished":2505,"description":2506,"extension":2507,"faq":2508,"keyword":2517,"kind":2518,"meta":2519,"navigation":387,"path":2528,"seo":2529,"slug":2532,"stem":2533,"topics":2534,"type":2538,"__hash__":2539},"docs\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Faudit-every-formula-in-a-workbook-with-python\u002Findex.md","Audit Every Formula in a Workbook with Python",{"type":7,"value":8,"toc":2491},"minimark",[9,19,285,290,319,326,330,345,602,605,609,612,965,972,1172,1176,1191,1451,1467,1471,1478,1731,1752,1756,1763,1974,1982,2137,2141,2144,2340,2347,2351,2396,2400,2403,2407,2418,2422,2428,2434,2445,2454,2458,2487],[10,11,12,13,18],"p",{},"Spreadsheets that have been in use for a few years accumulate formula debt: a total that multiplies by a hard-coded 1.2 \"just for this quarter\", one row in a column of identical formulas that points at the wrong cell, a number typed over a formula to make the figure match, a link to a colleague's workbook on a drive that no longer exists. Excel shows some of these one cell at a time. A Python audit shows all of them at once, for every sheet, in a table you can sort, filter and keep. This page, part of ",[14,15,17],"a",{"href":16},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002F","Working with Excel Formulas in Python",", builds that audit step by step.",[20,21,29,30,29,34,29,38,29,46,29,53,29,62,29,66,29,72,29,76,29,80,29,84,29,88,29,92,29,96,29,100,29,105,29,107,29,111,29,113,29,116,29,118,29,121,29,123,29,126,29,129,29,133,29,135,29,138,29,140,29,142,29,144,29,147,29,149,29,152,29,155,29,159,29,161,29,164,29,166,29,169,29,171,29,174,29,176,29,179,29,182,29,185,29,187,29,190,29,192,29,194,29,196,29,199,29,204,29,208,29,211,29,215,29,217,29,220,29,222,29,224,29,226,29,228,29,232,29,235,29,238,29,242,29,244,29,247,29,249,29,251,29,253,29,255,29,257,29,260,29,263,29,267,29,269,29,272,29,274,29,276,29,278,29,280,29,282],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"223 0 314 284","img",[25,26],"aud-sheet-t","aud-sheet-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:314px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"A column of formulas with two problems hidden in it",[35,36,37],"desc",{"id":26},"The Orders sheet total column. Most rows hold =B*C formulas for their own row. D4 multiplies by a hard-coded 1.2, D5 holds the typed number 3 instead of a formula, and D6 multiplies by C7 instead of C6. From a distance the column looks uniform.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"223","0","314","284","#ffffff",[47,48,52],"text",{"x":49,"y":50,"style":51},"380.0","31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Orders!D2:D7 — spot the problems",[39,54],{"x":55,"y":56,"width":57,"height":58,"rx":42,"fill":59,"stroke":60,"style":61},"239.3","48.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[39,63],{"x":64,"y":56,"width":65,"height":58,"rx":42,"fill":59,"stroke":60,"style":61},"271.3","51.0",[47,67,71],{"x":68,"y":69,"style":70},"296.8","65.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[39,73],{"x":74,"y":56,"width":75,"height":58,"rx":42,"fill":59,"stroke":60,"style":61},"322.3","44.1",[47,77,79],{"x":78,"y":69,"style":70},"344.4","B",[39,81],{"x":82,"y":56,"width":83,"height":58,"rx":42,"fill":59,"stroke":60,"style":61},"366.4","55.4",[47,85,87],{"x":86,"y":69,"style":70},"394.1","C",[39,89],{"x":90,"y":56,"width":91,"height":58,"rx":42,"fill":59,"stroke":60,"style":61},"421.8","98.9",[47,93,95],{"x":94,"y":69,"style":70},"471.3","D",[39,97],{"x":55,"y":98,"width":57,"height":99,"rx":42,"fill":59,"stroke":60,"style":61},"72.0","28.0",[47,101,104],{"x":102,"y":103,"style":70},"255.3","91.0","1",[39,106],{"x":64,"y":98,"width":65,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,108,110],{"x":68,"y":103,"style":109},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","item",[39,112],{"x":74,"y":98,"width":75,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,114,115],{"x":78,"y":103,"style":109},"qty",[39,117],{"x":82,"y":98,"width":83,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,119,120],{"x":86,"y":103,"style":109},"price",[39,122],{"x":90,"y":98,"width":91,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,124,125],{"x":94,"y":103,"style":109},"total",[39,127],{"x":55,"y":128,"width":57,"height":99,"rx":42,"fill":59,"stroke":60,"style":61},"100.0",[47,130,132],{"x":102,"y":131,"style":70},"119.0","2",[39,134],{"x":64,"y":128,"width":65,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,136,14],{"x":68,"y":131,"style":137},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[39,139],{"x":74,"y":128,"width":75,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,141,132],{"x":78,"y":131,"style":137},[39,143],{"x":82,"y":128,"width":83,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,145,146],{"x":86,"y":131,"style":137},"3.5",[39,148],{"x":90,"y":128,"width":91,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,150,151],{"x":94,"y":131,"style":137},"=B2*C2",[39,153],{"x":55,"y":154,"width":57,"height":99,"rx":42,"fill":59,"stroke":60,"style":61},"128.0",[47,156,158],{"x":102,"y":157,"style":70},"147.0","3",[39,160],{"x":64,"y":154,"width":65,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,162,163],{"x":68,"y":157,"style":137},"b",[39,165],{"x":74,"y":154,"width":75,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,167,168],{"x":78,"y":157,"style":137},"4",[39,170],{"x":82,"y":154,"width":83,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,172,173],{"x":86,"y":157,"style":137},"1.25",[39,175],{"x":90,"y":154,"width":91,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,177,178],{"x":94,"y":157,"style":137},"=B3*C3",[39,180],{"x":55,"y":181,"width":57,"height":99,"rx":42,"fill":59,"stroke":60,"style":61},"156.0",[47,183,168],{"x":102,"y":184,"style":70},"175.0",[39,186],{"x":64,"y":181,"width":65,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,188,189],{"x":68,"y":184,"style":137},"c",[39,191],{"x":74,"y":181,"width":75,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,193,104],{"x":78,"y":184,"style":137},[39,195],{"x":82,"y":181,"width":83,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,197,198],{"x":86,"y":184,"style":137},"9",[39,200],{"x":90,"y":181,"width":91,"height":99,"rx":42,"fill":201,"stroke":202,"style":203},"#fdefd8","var(--gold,#b4740a)","stroke-width:1.5px",[47,205,207],{"x":94,"y":184,"style":206},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","=B4*C4*1.2",[39,209],{"x":55,"y":210,"width":57,"height":99,"rx":42,"fill":59,"stroke":60,"style":61},"184.0",[47,212,214],{"x":102,"y":213,"style":70},"203.0","5",[39,216],{"x":64,"y":210,"width":65,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,218,219],{"x":68,"y":213,"style":137},"d",[39,221],{"x":74,"y":210,"width":75,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,223,158],{"x":78,"y":213,"style":137},[39,225],{"x":82,"y":210,"width":83,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,227,132],{"x":86,"y":213,"style":137},[39,229],{"x":90,"y":210,"width":91,"height":99,"rx":42,"fill":230,"stroke":231,"style":203},"#fee8f2","var(--accent,#f43f8f)",[47,233,158],{"x":94,"y":213,"style":234},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[39,236],{"x":55,"y":237,"width":57,"height":99,"rx":42,"fill":59,"stroke":60,"style":61},"212.0",[47,239,241],{"x":102,"y":240,"style":70},"231.0","6",[39,243],{"x":64,"y":237,"width":65,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,245,246],{"x":68,"y":240,"style":137},"e",[39,248],{"x":74,"y":237,"width":75,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,250,214],{"x":78,"y":240,"style":137},[39,252],{"x":82,"y":237,"width":83,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,254,168],{"x":86,"y":240,"style":137},[39,256],{"x":90,"y":237,"width":91,"height":99,"rx":42,"fill":230,"stroke":231,"style":203},[47,258,259],{"x":94,"y":240,"style":234},"=B6*C7",[39,261],{"x":55,"y":262,"width":57,"height":99,"rx":42,"fill":59,"stroke":60,"style":61},"240.0",[47,264,266],{"x":102,"y":265,"style":70},"259.0","7",[39,268],{"x":64,"y":262,"width":65,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,270,271],{"x":68,"y":265,"style":137},"f",[39,273],{"x":74,"y":262,"width":75,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,275,132],{"x":78,"y":265,"style":137},[39,277],{"x":82,"y":262,"width":83,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,279,241],{"x":86,"y":265,"style":137},[39,281],{"x":90,"y":262,"width":91,"height":99,"rx":42,"fill":45,"stroke":60,"style":61},[47,283,284],{"x":94,"y":265,"style":137},"=B7*C7",[286,287,289],"h2",{"id":288},"prerequisites","Prerequisites",[291,292,297],"pre",{"className":293,"code":294,"language":295,"meta":296,"style":296},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[298,299,300],"code",{"__ignoreMap":296},[301,302,305,309,313,316],"span",{"class":303,"line":304},"line",1,[301,306,308],{"class":307},"sMTad","pip",[301,310,312],{"class":311},"srMev"," install",[301,314,315],{"class":311}," openpyxl",[301,317,318],{"class":311}," pandas\n",[10,320,321,322,325],{},"The examples use ",[298,323,324],{},"orders.xlsx",": an Orders sheet whose total column has the three problems in the diagram, a lookup column into a Rates sheet, and a Summary sheet with a cross-sheet total, a link to an external workbook and a tax formula with a hard-coded rate.",[286,327,329],{"id":328},"step-1-build-the-inventory","Step 1 — Build the inventory",[10,331,332,333,336,337,340,341,344],{},"Load the workbook normally — not with ",[298,334,335],{},"data_only=True",", which would replace formulas with their cached results — and collect every cell whose ",[298,338,339],{},"data_type"," is ",[298,342,343],{},"'f'",":",[291,346,350],{"className":347,"code":348,"language":349,"meta":296,"style":296},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nfrom openpyxl import load_workbook\n\ndef formula_inventory(path):\n    wb = load_workbook(path)\n    rows = []\n    for ws in wb.worksheets:\n        for row in ws.iter_rows():\n            for cell in row:\n                if cell.data_type == \"f\":\n                    rows.append({\"sheet\": ws.title, \"cell\": cell.coordinate,\n                                 \"row\": cell.row, \"col\": cell.column_letter, \"formula\": cell.value})\n    return pd.DataFrame(rows)\n\ninv = formula_inventory(\"orders.xlsx\")\nprint(len(inv), \"formulas\")\nprint(inv.head(4).to_string(index=False))\n","python",[298,351,352,368,382,389,402,414,425,440,454,468,486,504,525,534,539,556,577],{"__ignoreMap":296},[301,353,354,358,362,365],{"class":303,"line":304},[301,355,357],{"class":356},"s-kum","import",[301,359,361],{"class":360},"skGVy"," pandas ",[301,363,364],{"class":356},"as",[301,366,367],{"class":360}," pd\n",[301,369,371,374,377,379],{"class":303,"line":370},2,[301,372,373],{"class":356},"from",[301,375,376],{"class":360}," openpyxl ",[301,378,357],{"class":356},[301,380,381],{"class":360}," load_workbook\n",[301,383,385],{"class":303,"line":384},3,[301,386,388],{"emptyLinePlaceholder":387},true,"\n",[301,390,392,395,399],{"class":303,"line":391},4,[301,393,394],{"class":356},"def",[301,396,398],{"class":397},"s_Opv"," formula_inventory",[301,400,401],{"class":360},"(path):\n",[301,403,405,408,411],{"class":303,"line":404},5,[301,406,407],{"class":360},"    wb ",[301,409,410],{"class":356},"=",[301,412,413],{"class":360}," load_workbook(path)\n",[301,415,417,420,422],{"class":303,"line":416},6,[301,418,419],{"class":360},"    rows ",[301,421,410],{"class":356},[301,423,424],{"class":360}," []\n",[301,426,428,431,434,437],{"class":303,"line":427},7,[301,429,430],{"class":356},"    for",[301,432,433],{"class":360}," ws ",[301,435,436],{"class":356},"in",[301,438,439],{"class":360}," wb.worksheets:\n",[301,441,443,446,449,451],{"class":303,"line":442},8,[301,444,445],{"class":356},"        for",[301,447,448],{"class":360}," row ",[301,450,436],{"class":356},[301,452,453],{"class":360}," ws.iter_rows():\n",[301,455,457,460,463,465],{"class":303,"line":456},9,[301,458,459],{"class":356},"            for",[301,461,462],{"class":360}," cell ",[301,464,436],{"class":356},[301,466,467],{"class":360}," row:\n",[301,469,471,474,477,480,483],{"class":303,"line":470},10,[301,472,473],{"class":356},"                if",[301,475,476],{"class":360}," cell.data_type ",[301,478,479],{"class":356},"==",[301,481,482],{"class":311}," \"f\"",[301,484,485],{"class":360},":\n",[301,487,489,492,495,498,501],{"class":303,"line":488},11,[301,490,491],{"class":360},"                    rows.append({",[301,493,494],{"class":311},"\"sheet\"",[301,496,497],{"class":360},": ws.title, ",[301,499,500],{"class":311},"\"cell\"",[301,502,503],{"class":360},": cell.coordinate,\n",[301,505,507,510,513,516,519,522],{"class":303,"line":506},12,[301,508,509],{"class":311},"                                 \"row\"",[301,511,512],{"class":360},": cell.row, ",[301,514,515],{"class":311},"\"col\"",[301,517,518],{"class":360},": cell.column_letter, ",[301,520,521],{"class":311},"\"formula\"",[301,523,524],{"class":360},": cell.value})\n",[301,526,528,531],{"class":303,"line":527},13,[301,529,530],{"class":356},"    return",[301,532,533],{"class":360}," pd.DataFrame(rows)\n",[301,535,537],{"class":303,"line":536},14,[301,538,388],{"emptyLinePlaceholder":387},[301,540,542,545,547,550,553],{"class":303,"line":541},15,[301,543,544],{"class":360},"inv ",[301,546,410],{"class":356},[301,548,549],{"class":360}," formula_inventory(",[301,551,552],{"class":311},"\"orders.xlsx\"",[301,554,555],{"class":360},")\n",[301,557,559,563,566,569,572,575],{"class":303,"line":558},16,[301,560,562],{"class":561},"sP0c6","print",[301,564,565],{"class":360},"(",[301,567,568],{"class":561},"len",[301,570,571],{"class":360},"(inv), ",[301,573,574],{"class":311},"\"formulas\"",[301,576,555],{"class":360},[301,578,580,582,585,587,590,594,596,599],{"class":303,"line":579},17,[301,581,562],{"class":561},[301,583,584],{"class":360},"(inv.head(",[301,586,168],{"class":561},[301,588,589],{"class":360},").to_string(",[301,591,593],{"class":592},"sa561","index",[301,595,410],{"class":356},[301,597,598],{"class":561},"False",[301,600,601],{"class":360},"))\n",[10,603,604],{},"That table alone answers the first audit questions — how many formulas, on which sheets — and it is the input to everything that follows.",[286,606,608],{"id":607},"step-2-take-each-formula-apart","Step 2 — Take each formula apart",[10,610,611],{},"openpyxl's formula tokenizer splits a formula into functions, operands and operators, which is far more reliable than regular expressions once formulas nest. From the tokens, pull out the functions used, the sheets referenced, any external workbook links, and numeric constants:",[291,613,615],{"className":347,"code":614,"language":349,"meta":296,"style":296},"from openpyxl.formula.tokenizer import Tokenizer, Token\n\ndef describe(formula):\n    funcs, sheets, external, numbers = set(), set(), False, []\n    for tok in Tokenizer(formula).items:\n        if tok.type == Token.FUNC and tok.subtype == Token.OPEN:\n            funcs.add(tok.value.rstrip(\"(\"))\n        elif tok.type == Token.OPERAND and tok.subtype == Token.RANGE and \"!\" in tok.value:\n            ref_sheet = tok.value.rsplit(\"!\", 1)[0].strip(\"'\")\n            if ref_sheet.startswith(\"[\"):\n                external = True\n            sheets.add(ref_sheet)\n        elif tok.type == Token.OPERAND and tok.subtype == Token.NUMBER:\n            numbers.append(float(tok.value))\n    return pd.Series({\"functions\": \",\".join(sorted(funcs)), \"refs_sheets\": \",\".join(sorted(sheets)),\n                      \"external\": external, \"constants\": numbers})\n\ninv = inv.join(inv[\"formula\"].apply(describe))\nprint(inv[[\"sheet\", \"cell\", \"functions\", \"refs_sheets\", \"external\", \"constants\"]].to_string(index=False))\n",[298,616,617,629,633,643,666,678,709,719,755,786,800,810,815,840,851,890,904,908,923],{"__ignoreMap":296},[301,618,619,621,624,626],{"class":303,"line":304},[301,620,373],{"class":356},[301,622,623],{"class":360}," openpyxl.formula.tokenizer ",[301,625,357],{"class":356},[301,627,628],{"class":360}," Tokenizer, Token\n",[301,630,631],{"class":303,"line":370},[301,632,388],{"emptyLinePlaceholder":387},[301,634,635,637,640],{"class":303,"line":384},[301,636,394],{"class":356},[301,638,639],{"class":397}," describe",[301,641,642],{"class":360},"(formula):\n",[301,644,645,648,650,653,656,659,661,663],{"class":303,"line":391},[301,646,647],{"class":360},"    funcs, sheets, external, numbers ",[301,649,410],{"class":356},[301,651,652],{"class":561}," set",[301,654,655],{"class":360},"(), ",[301,657,658],{"class":561},"set",[301,660,655],{"class":360},[301,662,598],{"class":561},[301,664,665],{"class":360},", []\n",[301,667,668,670,673,675],{"class":303,"line":404},[301,669,430],{"class":356},[301,671,672],{"class":360}," tok ",[301,674,436],{"class":356},[301,676,677],{"class":360}," Tokenizer(formula).items:\n",[301,679,680,683,686,688,691,694,697,700,702,704,707],{"class":303,"line":416},[301,681,682],{"class":356},"        if",[301,684,685],{"class":360}," tok.type ",[301,687,479],{"class":356},[301,689,690],{"class":360}," Token.",[301,692,693],{"class":561},"FUNC",[301,695,696],{"class":356}," and",[301,698,699],{"class":360}," tok.subtype ",[301,701,479],{"class":356},[301,703,690],{"class":360},[301,705,706],{"class":561},"OPEN",[301,708,485],{"class":360},[301,710,711,714,717],{"class":303,"line":427},[301,712,713],{"class":360},"            funcs.add(tok.value.rstrip(",[301,715,716],{"class":311},"\"(\"",[301,718,601],{"class":360},[301,720,721,724,726,728,730,733,735,737,739,741,744,746,749,752],{"class":303,"line":442},[301,722,723],{"class":356},"        elif",[301,725,685],{"class":360},[301,727,479],{"class":356},[301,729,690],{"class":360},[301,731,732],{"class":561},"OPERAND",[301,734,696],{"class":356},[301,736,699],{"class":360},[301,738,479],{"class":356},[301,740,690],{"class":360},[301,742,743],{"class":561},"RANGE",[301,745,696],{"class":356},[301,747,748],{"class":311}," \"!\"",[301,750,751],{"class":356}," in",[301,753,754],{"class":360}," tok.value:\n",[301,756,757,760,762,765,768,771,773,776,778,781,784],{"class":303,"line":456},[301,758,759],{"class":360},"            ref_sheet ",[301,761,410],{"class":356},[301,763,764],{"class":360}," tok.value.rsplit(",[301,766,767],{"class":311},"\"!\"",[301,769,770],{"class":360},", ",[301,772,104],{"class":561},[301,774,775],{"class":360},")[",[301,777,42],{"class":561},[301,779,780],{"class":360},"].strip(",[301,782,783],{"class":311},"\"'\"",[301,785,555],{"class":360},[301,787,788,791,794,797],{"class":303,"line":470},[301,789,790],{"class":356},"            if",[301,792,793],{"class":360}," ref_sheet.startswith(",[301,795,796],{"class":311},"\"[\"",[301,798,799],{"class":360},"):\n",[301,801,802,805,807],{"class":303,"line":488},[301,803,804],{"class":360},"                external ",[301,806,410],{"class":356},[301,808,809],{"class":561}," True\n",[301,811,812],{"class":303,"line":506},[301,813,814],{"class":360},"            sheets.add(ref_sheet)\n",[301,816,817,819,821,823,825,827,829,831,833,835,838],{"class":303,"line":527},[301,818,723],{"class":356},[301,820,685],{"class":360},[301,822,479],{"class":356},[301,824,690],{"class":360},[301,826,732],{"class":561},[301,828,696],{"class":356},[301,830,699],{"class":360},[301,832,479],{"class":356},[301,834,690],{"class":360},[301,836,837],{"class":561},"NUMBER",[301,839,485],{"class":360},[301,841,842,845,848],{"class":303,"line":536},[301,843,844],{"class":360},"            numbers.append(",[301,846,847],{"class":561},"float",[301,849,850],{"class":360},"(tok.value))\n",[301,852,853,855,858,861,864,867,870,873,876,879,881,883,885,887],{"class":303,"line":541},[301,854,530],{"class":356},[301,856,857],{"class":360}," pd.Series({",[301,859,860],{"class":311},"\"functions\"",[301,862,863],{"class":360},": ",[301,865,866],{"class":311},"\",\"",[301,868,869],{"class":360},".join(",[301,871,872],{"class":561},"sorted",[301,874,875],{"class":360},"(funcs)), ",[301,877,878],{"class":311},"\"refs_sheets\"",[301,880,863],{"class":360},[301,882,866],{"class":311},[301,884,869],{"class":360},[301,886,872],{"class":561},[301,888,889],{"class":360},"(sheets)),\n",[301,891,892,895,898,901],{"class":303,"line":558},[301,893,894],{"class":311},"                      \"external\"",[301,896,897],{"class":360},": external, ",[301,899,900],{"class":311},"\"constants\"",[301,902,903],{"class":360},": numbers})\n",[301,905,906],{"class":303,"line":579},[301,907,388],{"emptyLinePlaceholder":387},[301,909,911,913,915,918,920],{"class":303,"line":910},18,[301,912,544],{"class":360},[301,914,410],{"class":356},[301,916,917],{"class":360}," inv.join(inv[",[301,919,521],{"class":311},[301,921,922],{"class":360},"].apply(describe))\n",[301,924,926,928,931,933,935,937,939,941,943,945,947,950,952,954,957,959,961,963],{"class":303,"line":925},19,[301,927,562],{"class":561},[301,929,930],{"class":360},"(inv[[",[301,932,494],{"class":311},[301,934,770],{"class":360},[301,936,500],{"class":311},[301,938,770],{"class":360},[301,940,860],{"class":311},[301,942,770],{"class":360},[301,944,878],{"class":311},[301,946,770],{"class":360},[301,948,949],{"class":311},"\"external\"",[301,951,770],{"class":360},[301,953,900],{"class":311},[301,955,956],{"class":360},"]].to_string(",[301,958,593],{"class":592},[301,960,410],{"class":356},[301,962,598],{"class":561},[301,964,601],{"class":360},[10,966,967,968,971],{},"Now each formula has a profile. Sorting or filtering the table answers questions that are tedious in Excel: which sheets does Summary depend on, where is ",[298,969,970],{},"VLOOKUP"," used, which cells link outside the file.",[20,973,29,979,29,982,29,985,29,989,29,997,29,1002,29,1062,29,1065,29,1074,29,1077,29,1083,29,1086,29,1091,29,1094,29,1098,29,1100,29,1105,29,1108,29,1113,29,1116,29,1120,29,1122,29,1127,29,1135,29,1139,29,1142,29,1146,29,1151,29,1154,29,1156,29,1160,29,1163,29,1166,29,1168],{"viewBox":974,"role":23,"ariaLabelledBy":975,"xmlns":27,"style":978},"4 0 752 240",[976,977],"aud-tokens-t","aud-tokens-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,980,981],{"id":976},"What the tokenizer sees in one formula",[35,983,984],{"id":977},"The formula =VLOOKUP(A2,Rates!A:B,2,FALSE)*1.2 split into tokens. Callouts mark the function token VLOOKUP, the cross-sheet range operand Rates!A:B, and the numeric constant 1.2, which the audit reports as a hard-coded number.",[39,986],{"x":168,"y":42,"width":987,"height":988,"fill":45},"752","240",[39,990],{"x":991,"y":992,"width":993,"height":994,"rx":995,"fill":996,"stroke":60,"style":203},"20.0","16.0","720.0","150.0","12","#f0f2f5",[47,998,104],{"x":999,"y":1000,"style":1001},"60.0","47.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",[47,1003,1006,1010,1012,1014,1017,1020,1023,1026,1029,1031,1033,1035,1037,1039,1042,1044,1046,1049,1052,1054,1057,1059],{"x":1004,"y":1000,"style":1005},"82.0","font-size:12.5px;text-anchor:start;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",[1007,1008,410],"tspan",{"style":1009},"fill:var(--text,#172033);font-weight:400",[1007,1011,970],{"style":1009},[1007,1013,565],{"style":1009},[1007,1015,1016],{"style":1009},"A2",[1007,1018,1019],{"style":1009},",",[1007,1021,1022],{"style":1009}," ",[1007,1024,1025],{"style":1009},"Rates",[1007,1027,1028],{"style":1009},"!",[1007,1030,71],{"style":1009},[1007,1032,344],{"style":1009},[1007,1034,79],{"style":1009},[1007,1036,1019],{"style":1009},[1007,1038,1022],{"style":1009},[1007,1040,132],{"style":1041},"fill:var(--gold-ink,#7a4e06);font-weight:400",[1007,1043,1019],{"style":1009},[1007,1045,1022],{"style":1009},[1007,1047,1048],{"style":1009},"FALSE",[1007,1050,1051],{"style":1009},")",[1007,1053,1022],{"style":1009},[1007,1055,1056],{"style":1009},"*",[1007,1058,1022],{"style":1009},[1007,1060,1061],{"style":1041},"1.2",[47,1063,132],{"x":999,"y":1064,"style":1001},"68.0",[1066,1067],"circle",{"cx":1068,"cy":1069,"r":1070,"fill":1071,"stroke":1072,"style":1073},"36.0","63.5","11.0","#4338ca","none","stroke-width:0px",[47,1075,104],{"x":1068,"y":1064,"style":1076},"font-size:12px;font-weight:700;fill:#ffffff;text-anchor:middle",[47,1078,1079],{"x":1004,"y":1064,"style":1005},[1007,1080,1082],{"style":1081},"fill:var(--muted,#5b6780);font-weight:400","# FUNC     VLOOKUP(",[47,1084,158],{"x":999,"y":1085,"style":1001},"89.0",[47,1087,1088],{"x":1004,"y":1085,"style":1005},[1007,1089,1090],{"style":1081},"# OPERAND  A2            RANGE",[47,1092,168],{"x":999,"y":1093,"style":1001},"110.0",[1066,1095],{"cx":1068,"cy":1096,"r":1070,"fill":1097,"stroke":1072,"style":1073},"105.5","#0b6157",[47,1099,132],{"x":1068,"y":1093,"style":1076},[47,1101,1102],{"x":1004,"y":1093,"style":1005},[1007,1103,1104],{"style":1081},"# OPERAND  Rates!A:B     RANGE  -> sheet Rates",[47,1106,214],{"x":999,"y":1107,"style":1001},"131.0",[47,1109,1110],{"x":1004,"y":1107,"style":1005},[1007,1111,1112],{"style":1081},"# OPERAND  2             NUMBER",[47,1114,241],{"x":999,"y":1115,"style":1001},"152.0",[1066,1117],{"cx":1068,"cy":1118,"r":1070,"fill":1119,"stroke":1072,"style":1073},"147.5","#8a5808",[47,1121,158],{"x":1068,"y":1115,"style":1076},[47,1123,1124],{"x":1004,"y":1115,"style":1005},[1007,1125,1126],{"style":1081},"# OPERAND  1.2           NUMBER -> constant",[39,1128],{"x":991,"y":1129,"width":1130,"height":1131,"rx":1132,"fill":1133,"stroke":1134,"style":203},"180.0","230.7","44.0","10","#f0f4ff","var(--brand,#5b5cf0)",[1066,1136],{"cx":1137,"cy":1138,"r":1070,"fill":1071,"stroke":1072,"style":1073},"40.0","202.0",[47,1140,104],{"x":1137,"y":1141,"style":1076},"206.5",[47,1143,1145],{"x":999,"y":1141,"style":1144},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","function used: VLOOKUP",[39,1147],{"x":1148,"y":1129,"width":1130,"height":1131,"rx":1132,"fill":1149,"stroke":1150,"style":203},"264.7","#d9f4f1","var(--teal,#0f9488)",[1066,1152],{"cx":1153,"cy":1138,"r":1070,"fill":1097,"stroke":1072,"style":1073},"284.7",[47,1155,132],{"x":1153,"y":1141,"style":1076},[47,1157,1159],{"x":1158,"y":1141,"style":1144},"304.7","depends on sheet Rates",[39,1161],{"x":1162,"y":1129,"width":1130,"height":1131,"rx":1132,"fill":201,"stroke":202,"style":203},"509.3",[1066,1164],{"cx":1165,"cy":1138,"r":1070,"fill":1119,"stroke":1072,"style":1073},"529.3",[47,1167,158],{"x":1165,"y":1141,"style":1076},[47,1169,1171],{"x":1170,"y":1141,"style":1144},"549.3","hard-coded factor",[286,1173,1175],{"id":1174},"step-3-flag-hard-coded-numbers","Step 3 — Flag hard-coded numbers",[10,1177,1178,1179,1181,1182,1184,1185,1187,1188,1190],{},"Numbers inside formulas are sometimes harmless — the ",[298,1180,132],{}," in a ",[298,1183,970],{}," is a column index, and ",[298,1186,42],{}," and ",[298,1189,104],{}," appear in comparisons and switches — and sometimes a hidden assumption: a tax rate, a markup, a threshold. Filter out the harmless ones by value and by position, and review the rest:",[291,1192,1194],{"className":347,"code":1193,"language":349,"meta":296,"style":296},"LOOKUPS = {\"VLOOKUP\", \"HLOOKUP\", \"INDEX\", \"MATCH\", \"OFFSET\"}\n\ndef suspicious_constants(row):\n    if row[\"functions\"] and set(row[\"functions\"].split(\",\")) & LOOKUPS:\n        return [n for n in row[\"constants\"] if n not in (0, 1) and not float(n).is_integer()]\n    return [n for n in row[\"constants\"] if n not in (0, 1)]\n\ninv[\"hard_coded\"] = inv.apply(suspicious_constants, axis=1)\nprint(inv.loc[inv[\"hard_coded\"].str.len() > 0, [\"sheet\", \"cell\", \"formula\", \"hard_coded\"]].to_string(index=False))\n",[298,1195,1196,1233,1237,1247,1286,1341,1378,1382,1406],{"__ignoreMap":296},[301,1197,1198,1201,1204,1207,1210,1212,1215,1217,1220,1222,1225,1227,1230],{"class":303,"line":304},[301,1199,1200],{"class":561},"LOOKUPS",[301,1202,1203],{"class":356}," =",[301,1205,1206],{"class":360}," {",[301,1208,1209],{"class":311},"\"VLOOKUP\"",[301,1211,770],{"class":360},[301,1213,1214],{"class":311},"\"HLOOKUP\"",[301,1216,770],{"class":360},[301,1218,1219],{"class":311},"\"INDEX\"",[301,1221,770],{"class":360},[301,1223,1224],{"class":311},"\"MATCH\"",[301,1226,770],{"class":360},[301,1228,1229],{"class":311},"\"OFFSET\"",[301,1231,1232],{"class":360},"}\n",[301,1234,1235],{"class":303,"line":370},[301,1236,388],{"emptyLinePlaceholder":387},[301,1238,1239,1241,1244],{"class":303,"line":384},[301,1240,394],{"class":356},[301,1242,1243],{"class":397}," suspicious_constants",[301,1245,1246],{"class":360},"(row):\n",[301,1248,1249,1252,1255,1257,1260,1263,1265,1268,1270,1273,1275,1278,1281,1284],{"class":303,"line":391},[301,1250,1251],{"class":356},"    if",[301,1253,1254],{"class":360}," row[",[301,1256,860],{"class":311},[301,1258,1259],{"class":360},"] ",[301,1261,1262],{"class":356},"and",[301,1264,652],{"class":561},[301,1266,1267],{"class":360},"(row[",[301,1269,860],{"class":311},[301,1271,1272],{"class":360},"].split(",[301,1274,866],{"class":311},[301,1276,1277],{"class":360},")) ",[301,1279,1280],{"class":356},"&",[301,1282,1283],{"class":561}," LOOKUPS",[301,1285,485],{"class":360},[301,1287,1288,1291,1294,1297,1300,1302,1304,1306,1308,1311,1313,1316,1318,1321,1323,1325,1327,1330,1332,1335,1338],{"class":303,"line":404},[301,1289,1290],{"class":356},"        return",[301,1292,1293],{"class":360}," [n ",[301,1295,1296],{"class":356},"for",[301,1298,1299],{"class":360}," n ",[301,1301,436],{"class":356},[301,1303,1254],{"class":360},[301,1305,900],{"class":311},[301,1307,1259],{"class":360},[301,1309,1310],{"class":356},"if",[301,1312,1299],{"class":360},[301,1314,1315],{"class":356},"not",[301,1317,751],{"class":356},[301,1319,1320],{"class":360}," (",[301,1322,42],{"class":561},[301,1324,770],{"class":360},[301,1326,104],{"class":561},[301,1328,1329],{"class":360},") ",[301,1331,1262],{"class":356},[301,1333,1334],{"class":356}," not",[301,1336,1337],{"class":561}," float",[301,1339,1340],{"class":360},"(n).is_integer()]\n",[301,1342,1343,1345,1347,1349,1351,1353,1355,1357,1359,1361,1363,1365,1367,1369,1371,1373,1375],{"class":303,"line":416},[301,1344,530],{"class":356},[301,1346,1293],{"class":360},[301,1348,1296],{"class":356},[301,1350,1299],{"class":360},[301,1352,436],{"class":356},[301,1354,1254],{"class":360},[301,1356,900],{"class":311},[301,1358,1259],{"class":360},[301,1360,1310],{"class":356},[301,1362,1299],{"class":360},[301,1364,1315],{"class":356},[301,1366,751],{"class":356},[301,1368,1320],{"class":360},[301,1370,42],{"class":561},[301,1372,770],{"class":360},[301,1374,104],{"class":561},[301,1376,1377],{"class":360},")]\n",[301,1379,1380],{"class":303,"line":427},[301,1381,388],{"emptyLinePlaceholder":387},[301,1383,1384,1387,1390,1392,1394,1397,1400,1402,1404],{"class":303,"line":442},[301,1385,1386],{"class":360},"inv[",[301,1388,1389],{"class":311},"\"hard_coded\"",[301,1391,1259],{"class":360},[301,1393,410],{"class":356},[301,1395,1396],{"class":360}," inv.apply(suspicious_constants, ",[301,1398,1399],{"class":592},"axis",[301,1401,410],{"class":356},[301,1403,104],{"class":561},[301,1405,555],{"class":360},[301,1407,1408,1410,1413,1415,1418,1421,1424,1427,1429,1431,1433,1435,1437,1439,1441,1443,1445,1447,1449],{"class":303,"line":456},[301,1409,562],{"class":561},[301,1411,1412],{"class":360},"(inv.loc[inv[",[301,1414,1389],{"class":311},[301,1416,1417],{"class":360},"].str.len() ",[301,1419,1420],{"class":356},">",[301,1422,1423],{"class":561}," 0",[301,1425,1426],{"class":360},", [",[301,1428,494],{"class":311},[301,1430,770],{"class":360},[301,1432,500],{"class":311},[301,1434,770],{"class":360},[301,1436,521],{"class":311},[301,1438,770],{"class":360},[301,1440,1389],{"class":311},[301,1442,956],{"class":360},[301,1444,593],{"class":592},[301,1446,410],{"class":356},[301,1448,598],{"class":561},[301,1450,601],{"class":360},[10,1452,1453,1454,1456,1457,1187,1460,1456,1463,1466],{},"The audit reports ",[298,1455,1061],{}," in ",[298,1458,1459],{},"Orders!D4",[298,1461,1462],{},"0.2",[298,1464,1465],{},"Summary!B3",". The fix for both is the same: move the number into a labelled input cell — a \"Tax rate\" cell on an Assumptions sheet — and refer to it, so the assumption is visible, documented and changed in one place.",[286,1468,1470],{"id":1469},"step-4-find-the-inconsistent-formula-in-a-column","Step 4 — Find the inconsistent formula in a column",[10,1472,1473,1474,1477],{},"A column of per-row formulas should all follow one pattern. To compare them, translate each formula back to the position of one reference row with openpyxl's ",[298,1475,1476],{},"Translator","; formulas that follow the pattern become identical text:",[291,1479,1481],{"className":347,"code":1480,"language":349,"meta":296,"style":296},"from openpyxl.formula.translate import Translator\n\ndef normalised(row, anchor_row=2):\n    target = f\"{row['col']}{anchor_row}\"\n    return Translator(row[\"formula\"], origin=row[\"cell\"]).translate_formula(target)\n\norders = inv[inv[\"sheet\"] == \"Orders\"].copy()\norders[\"pattern\"] = orders.apply(normalised, axis=1)\nfor col, group in orders.groupby(\"col\"):\n    usual = group[\"pattern\"].mode()[0]\n    odd = group[group[\"pattern\"] != usual]\n    print(f\"column {col}: usual {usual}; odd ones: {odd['cell'].tolist()}\")\n",[298,1482,1483,1495,1499,1515,1553,1577,1581,1603,1626,1642,1662,1682],{"__ignoreMap":296},[301,1484,1485,1487,1490,1492],{"class":303,"line":304},[301,1486,373],{"class":356},[301,1488,1489],{"class":360}," openpyxl.formula.translate ",[301,1491,357],{"class":356},[301,1493,1494],{"class":360}," Translator\n",[301,1496,1497],{"class":303,"line":370},[301,1498,388],{"emptyLinePlaceholder":387},[301,1500,1501,1503,1506,1509,1511,1513],{"class":303,"line":384},[301,1502,394],{"class":356},[301,1504,1505],{"class":397}," normalised",[301,1507,1508],{"class":360},"(row, anchor_row",[301,1510,410],{"class":356},[301,1512,132],{"class":561},[301,1514,799],{"class":360},[301,1516,1517,1520,1522,1525,1528,1532,1535,1538,1541,1544,1547,1550],{"class":303,"line":391},[301,1518,1519],{"class":360},"    target ",[301,1521,410],{"class":356},[301,1523,1524],{"class":356}," f",[301,1526,1527],{"class":311},"\"",[301,1529,1531],{"class":1530},"sSjpA","{",[301,1533,1534],{"class":360},"row[",[301,1536,1537],{"class":311},"'col'",[301,1539,1540],{"class":360},"]",[301,1542,1543],{"class":1530},"}{",[301,1545,1546],{"class":360},"anchor_row",[301,1548,1549],{"class":1530},"}",[301,1551,1552],{"class":311},"\"\n",[301,1554,1555,1557,1560,1562,1565,1568,1570,1572,1574],{"class":303,"line":404},[301,1556,530],{"class":356},[301,1558,1559],{"class":360}," Translator(row[",[301,1561,521],{"class":311},[301,1563,1564],{"class":360},"], ",[301,1566,1567],{"class":592},"origin",[301,1569,410],{"class":356},[301,1571,1534],{"class":360},[301,1573,500],{"class":311},[301,1575,1576],{"class":360},"]).translate_formula(target)\n",[301,1578,1579],{"class":303,"line":416},[301,1580,388],{"emptyLinePlaceholder":387},[301,1582,1583,1586,1588,1591,1593,1595,1597,1600],{"class":303,"line":427},[301,1584,1585],{"class":360},"orders ",[301,1587,410],{"class":356},[301,1589,1590],{"class":360}," inv[inv[",[301,1592,494],{"class":311},[301,1594,1259],{"class":360},[301,1596,479],{"class":356},[301,1598,1599],{"class":311}," \"Orders\"",[301,1601,1602],{"class":360},"].copy()\n",[301,1604,1605,1608,1611,1613,1615,1618,1620,1622,1624],{"class":303,"line":442},[301,1606,1607],{"class":360},"orders[",[301,1609,1610],{"class":311},"\"pattern\"",[301,1612,1259],{"class":360},[301,1614,410],{"class":356},[301,1616,1617],{"class":360}," orders.apply(normalised, ",[301,1619,1399],{"class":592},[301,1621,410],{"class":356},[301,1623,104],{"class":561},[301,1625,555],{"class":360},[301,1627,1628,1630,1633,1635,1638,1640],{"class":303,"line":456},[301,1629,1296],{"class":356},[301,1631,1632],{"class":360}," col, group ",[301,1634,436],{"class":356},[301,1636,1637],{"class":360}," orders.groupby(",[301,1639,515],{"class":311},[301,1641,799],{"class":360},[301,1643,1644,1647,1649,1652,1654,1657,1659],{"class":303,"line":470},[301,1645,1646],{"class":360},"    usual ",[301,1648,410],{"class":356},[301,1650,1651],{"class":360}," group[",[301,1653,1610],{"class":311},[301,1655,1656],{"class":360},"].mode()[",[301,1658,42],{"class":561},[301,1660,1661],{"class":360},"]\n",[301,1663,1664,1667,1669,1672,1674,1676,1679],{"class":303,"line":488},[301,1665,1666],{"class":360},"    odd ",[301,1668,410],{"class":356},[301,1670,1671],{"class":360}," group[group[",[301,1673,1610],{"class":311},[301,1675,1259],{"class":360},[301,1677,1678],{"class":356},"!=",[301,1680,1681],{"class":360}," usual]\n",[301,1683,1684,1687,1689,1691,1694,1696,1699,1701,1704,1706,1709,1711,1714,1716,1719,1722,1725,1727,1729],{"class":303,"line":506},[301,1685,1686],{"class":561},"    print",[301,1688,565],{"class":360},[301,1690,271],{"class":356},[301,1692,1693],{"class":311},"\"column ",[301,1695,1531],{"class":1530},[301,1697,1698],{"class":360},"col",[301,1700,1549],{"class":1530},[301,1702,1703],{"class":311},": usual ",[301,1705,1531],{"class":1530},[301,1707,1708],{"class":360},"usual",[301,1710,1549],{"class":1530},[301,1712,1713],{"class":311},"; odd ones: ",[301,1715,1531],{"class":1530},[301,1717,1718],{"class":360},"odd[",[301,1720,1721],{"class":311},"'cell'",[301,1723,1724],{"class":360},"].tolist()",[301,1726,1549],{"class":1530},[301,1728,1527],{"class":311},[301,1730,555],{"class":360},[10,1732,1733,1734,1736,1737,1740,1741,1187,1744,1747,1748,1751],{},"Column D's usual pattern is ",[298,1735,151],{},". ",[298,1738,1739],{},"D4"," normalises to ",[298,1742,1743],{},"=B2*C2*1.2",[298,1745,1746],{},"D6"," to ",[298,1749,1750],{},"=B2*C3"," — the second one points one row down, the classic copy-paste slip that gives a plausible-looking wrong number. Column E is entirely consistent. This is the same idea as Excel's \"inconsistent formula\" warning, applied to every column at once.",[286,1753,1755],{"id":1754},"step-5-find-values-typed-over-formulas","Step 5 — Find values typed over formulas",[10,1757,1758,1759,1762],{},"The third problem in the Orders sheet is not in the inventory at all: ",[298,1760,1761],{},"D5"," holds the number 3, typed over the formula that belonged there. Find cells that hold constants inside a column that is otherwise formulas:",[291,1764,1766],{"className":347,"code":1765,"language":349,"meta":296,"style":296},"wb = load_workbook(\"orders.xlsx\")\nws = wb[\"Orders\"]\nfor col, group in orders.groupby(\"col\"):\n    rows = range(group[\"row\"].min(), group[\"row\"].max() + 1)\n    typed = [f\"{col}{r}\" for r in rows\n             if ws[f\"{col}{r}\"].data_type != \"f\" and ws[f\"{col}{r}\"].value is not None]\n    print(f\"column {col}: constants among formulas: {typed}\")\n",[298,1767,1768,1782,1797,1811,1842,1880,1944],{"__ignoreMap":296},[301,1769,1770,1773,1775,1778,1780],{"class":303,"line":304},[301,1771,1772],{"class":360},"wb ",[301,1774,410],{"class":356},[301,1776,1777],{"class":360}," load_workbook(",[301,1779,552],{"class":311},[301,1781,555],{"class":360},[301,1783,1784,1787,1789,1792,1795],{"class":303,"line":370},[301,1785,1786],{"class":360},"ws ",[301,1788,410],{"class":356},[301,1790,1791],{"class":360}," wb[",[301,1793,1794],{"class":311},"\"Orders\"",[301,1796,1661],{"class":360},[301,1798,1799,1801,1803,1805,1807,1809],{"class":303,"line":384},[301,1800,1296],{"class":356},[301,1802,1632],{"class":360},[301,1804,436],{"class":356},[301,1806,1637],{"class":360},[301,1808,515],{"class":311},[301,1810,799],{"class":360},[301,1812,1813,1815,1817,1820,1823,1826,1829,1831,1834,1837,1840],{"class":303,"line":391},[301,1814,419],{"class":360},[301,1816,410],{"class":356},[301,1818,1819],{"class":561}," range",[301,1821,1822],{"class":360},"(group[",[301,1824,1825],{"class":311},"\"row\"",[301,1827,1828],{"class":360},"].min(), group[",[301,1830,1825],{"class":311},[301,1832,1833],{"class":360},"].max() ",[301,1835,1836],{"class":356},"+",[301,1838,1839],{"class":561}," 1",[301,1841,555],{"class":360},[301,1843,1844,1847,1849,1852,1854,1856,1858,1860,1862,1865,1867,1869,1872,1875,1877],{"class":303,"line":404},[301,1845,1846],{"class":360},"    typed ",[301,1848,410],{"class":356},[301,1850,1851],{"class":360}," [",[301,1853,271],{"class":356},[301,1855,1527],{"class":311},[301,1857,1531],{"class":1530},[301,1859,1698],{"class":360},[301,1861,1543],{"class":1530},[301,1863,1864],{"class":360},"r",[301,1866,1549],{"class":1530},[301,1868,1527],{"class":311},[301,1870,1871],{"class":356}," for",[301,1873,1874],{"class":360}," r ",[301,1876,436],{"class":356},[301,1878,1879],{"class":360}," rows\n",[301,1881,1882,1885,1888,1890,1892,1894,1896,1898,1900,1902,1904,1907,1909,1911,1913,1915,1917,1919,1921,1923,1925,1927,1929,1931,1934,1937,1939,1942],{"class":303,"line":416},[301,1883,1884],{"class":356},"             if",[301,1886,1887],{"class":360}," ws[",[301,1889,271],{"class":356},[301,1891,1527],{"class":311},[301,1893,1531],{"class":1530},[301,1895,1698],{"class":360},[301,1897,1543],{"class":1530},[301,1899,1864],{"class":360},[301,1901,1549],{"class":1530},[301,1903,1527],{"class":311},[301,1905,1906],{"class":360},"].data_type ",[301,1908,1678],{"class":356},[301,1910,482],{"class":311},[301,1912,696],{"class":356},[301,1914,1887],{"class":360},[301,1916,271],{"class":356},[301,1918,1527],{"class":311},[301,1920,1531],{"class":1530},[301,1922,1698],{"class":360},[301,1924,1543],{"class":1530},[301,1926,1864],{"class":360},[301,1928,1549],{"class":1530},[301,1930,1527],{"class":311},[301,1932,1933],{"class":360},"].value ",[301,1935,1936],{"class":356},"is",[301,1938,1334],{"class":356},[301,1940,1941],{"class":561}," None",[301,1943,1661],{"class":360},[301,1945,1946,1948,1950,1952,1954,1956,1958,1960,1963,1965,1968,1970,1972],{"class":303,"line":427},[301,1947,1686],{"class":561},[301,1949,565],{"class":360},[301,1951,271],{"class":356},[301,1953,1693],{"class":311},[301,1955,1531],{"class":1530},[301,1957,1698],{"class":360},[301,1959,1549],{"class":1530},[301,1961,1962],{"class":311},": constants among formulas: ",[301,1964,1531],{"class":1530},[301,1966,1967],{"class":360},"typed",[301,1969,1549],{"class":1530},[301,1971,1527],{"class":311},[301,1973,555],{"class":360},[10,1975,1976,1977,1981],{},"A typed-over formula is the most dangerous item on the list, because it is correct on the day it is typed and wrong every day after — the figure no longer follows its inputs. ",[14,1978,1980],{"href":1979},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fconvert-excel-formulas-to-values-with-python\u002F","Convert Excel Formulas to Values with Python"," covers the deliberate version of this change, when values are what you want.",[20,1983,29,1988,29,1991,29,1994,29,1997,29,2002,29,2012,29,2018,29,2021,29,2025,29,2032,29,2036,29,2041,29,2045,29,2051,29,2054,29,2060,29,2064,29,2068,29,2072,29,2078,29,2081,29,2086,29,2089,29,2094,29,2097,29,2104,29,2108,29,2112,29,2116,29,2121,29,2124,29,2129,29,2132],{"viewBox":1984,"role":23,"ariaLabelledBy":1985,"xmlns":27,"style":978},"4 0 752 207",[1986,1987],"aud-findings-t","aud-findings-d",[31,1989,1990],{"id":1986},"What the audit found in orders.xlsx",[35,1992,1993],{"id":1987},"Three panels of findings. Hard-coded numbers: 1.2 in Orders D4 and 0.2 in Summary B3. Inconsistent formulas: D4 and D6 differ from the usual =B2*C2 pattern, and D6 points one row down. Other risks: D5 holds a typed value inside a formula column, and Summary B2 links to an external workbook.",[39,1995],{"x":168,"y":42,"width":987,"height":1996,"fill":45},"207",[39,1998],{"x":991,"y":992,"width":1999,"height":184,"rx":2000,"fill":201,"stroke":202,"style":2001},"228.0","14","stroke-width:2px",[2003,2004,2006],"g",{"transform":2005},"translate(121.0,29.0) scale(1.083)",[2007,2008],"path",{"d":2009,"fill":1072,"stroke":2010,"style":2011},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[47,2013,2017],{"x":2014,"y":2015,"style":2016},"134.0","78.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Hard-coded numbers",[303,2019],{"x1":1068,"y1":103,"x2":2020,"y2":103,"stroke":202,"style":61},"232.0",[47,2022,2024],{"x":999,"y":2023,"style":1144},"111.0","Orders!D4: 1.2",[2003,2026,2028],{"transform":2027},"translate(38.5,99.5) scale(0.625)",[2007,2029],{"d":2030,"fill":1072,"stroke":202,"style":2031},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[47,2033,2035],{"x":999,"y":2034,"style":1144},"137.0","Summary!B3: 0.2",[2003,2037,2039],{"transform":2038},"translate(38.5,125.5) scale(0.625)",[2007,2040],{"d":2030,"fill":1072,"stroke":202,"style":2031},[47,2042,2044],{"x":999,"y":2043,"style":1144},"163.0","move to labelled inputs",[2003,2046,2048],{"transform":2047},"translate(38.5,151.5) scale(0.625)",[2007,2049],{"d":2050,"fill":1072,"stroke":1150,"style":2031},"M5 12.5l4.5 4.5L19 7",[39,2052],{"x":2053,"y":992,"width":1999,"height":184,"rx":2000,"fill":230,"stroke":231,"style":2001},"266.0",[2003,2055,2057],{"transform":2056},"translate(367.0,29.0) scale(1.083)",[2007,2058],{"d":2030,"fill":1072,"stroke":2059,"style":2011},"var(--accent-ink,#be185d)",[47,2061,2063],{"x":49,"y":2015,"style":2062},"font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Inconsistent formulas",[303,2065],{"x1":2066,"y1":103,"x2":2067,"y2":103,"stroke":231,"style":61},"282.0","478.0",[47,2069,2071],{"x":2070,"y":2023,"style":1144},"306.0","D4: extra factor",[2003,2073,2075],{"transform":2074},"translate(284.5,99.5) scale(0.625)",[2007,2076],{"d":2077,"fill":1072,"stroke":231,"style":2031},"M6 6l12 12 M18 6L6 18",[47,2079,2080],{"x":2070,"y":2034,"style":1144},"D6: refers to C7, not C6",[2003,2082,2084],{"transform":2083},"translate(284.5,125.5) scale(0.625)",[2007,2085],{"d":2077,"fill":1072,"stroke":231,"style":2031},[47,2087,2088],{"x":2070,"y":2043,"style":1144},"usual: =B2*C2",[2003,2090,2092],{"transform":2091},"translate(284.5,151.5) scale(0.625)",[2007,2093],{"d":2050,"fill":1072,"stroke":1150,"style":2031},[39,2095],{"x":2096,"y":992,"width":1999,"height":184,"rx":2000,"fill":1133,"stroke":1134,"style":2001},"512.0",[2003,2098,2100],{"transform":2099},"translate(613.0,29.0) scale(1.083)",[2007,2101],{"d":2102,"fill":1072,"stroke":2103,"style":2011},"M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1 M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1","var(--brand-strong,#4338ca)",[47,2105,2107],{"x":2106,"y":2015,"style":51},"626.0","Other risks",[303,2109],{"x1":2110,"y1":103,"x2":2111,"y2":103,"stroke":1134,"style":61},"528.0","724.0",[47,2113,2115],{"x":2114,"y":2023,"style":1144},"552.0","D5: value typed over formula",[2003,2117,2119],{"transform":2118},"translate(530.5,99.5) scale(0.625)",[2007,2120],{"d":2077,"fill":1072,"stroke":231,"style":2031},[47,2122,2123],{"x":2114,"y":2034,"style":1144},"Summary!B2: external link",[2003,2125,2127],{"transform":2126},"translate(530.5,125.5) scale(0.625)",[2007,2128],{"d":2077,"fill":1072,"stroke":231,"style":2031},[47,2130,2131],{"x":2114,"y":2043,"style":1144},"fix at the source",[2003,2133,2135],{"transform":2134},"translate(530.5,151.5) scale(0.625)",[2007,2136],{"d":2050,"fill":1072,"stroke":1150,"style":2031},[286,2138,2140],{"id":2139},"step-6-write-the-audit-into-the-workbook","Step 6 — Write the audit into the workbook",[10,2142,2143],{},"An audit is most useful where the workbook's owner will see it. Write the inventory and findings to a new sheet — in a copy, not the original, so the audit never changes the file it describes:",[291,2145,2147],{"className":347,"code":2146,"language":349,"meta":296,"style":296},"inv[\"inconsistent\"] = inv[\"cell\"].isin([\"D4\", \"D6\"]) & (inv[\"sheet\"] == \"Orders\")\nreport = inv[[\"sheet\", \"cell\", \"formula\", \"functions\", \"refs_sheets\", \"external\", \"hard_coded\", \"inconsistent\"]].copy()\nreport[\"hard_coded\"] = report[\"hard_coded\"].apply(lambda xs: \", \".join(str(x) for x in xs))\n\nwith pd.ExcelWriter(\"orders_audit.xlsx\") as writer:\n    report.to_excel(writer, sheet_name=\"Formula audit\", index=False)\nprint(pd.read_excel(\"orders_audit.xlsx\").shape)\n",[298,2148,2149,2194,2237,2283,2287,2305,2328],{"__ignoreMap":296},[301,2150,2151,2153,2156,2158,2160,2163,2165,2168,2171,2173,2176,2179,2181,2184,2186,2188,2190,2192],{"class":303,"line":304},[301,2152,1386],{"class":360},[301,2154,2155],{"class":311},"\"inconsistent\"",[301,2157,1259],{"class":360},[301,2159,410],{"class":356},[301,2161,2162],{"class":360}," inv[",[301,2164,500],{"class":311},[301,2166,2167],{"class":360},"].isin([",[301,2169,2170],{"class":311},"\"D4\"",[301,2172,770],{"class":360},[301,2174,2175],{"class":311},"\"D6\"",[301,2177,2178],{"class":360},"]) ",[301,2180,1280],{"class":356},[301,2182,2183],{"class":360}," (inv[",[301,2185,494],{"class":311},[301,2187,1259],{"class":360},[301,2189,479],{"class":356},[301,2191,1599],{"class":311},[301,2193,555],{"class":360},[301,2195,2196,2199,2201,2204,2206,2208,2210,2212,2214,2216,2218,2220,2222,2224,2226,2228,2230,2232,2234],{"class":303,"line":370},[301,2197,2198],{"class":360},"report ",[301,2200,410],{"class":356},[301,2202,2203],{"class":360}," inv[[",[301,2205,494],{"class":311},[301,2207,770],{"class":360},[301,2209,500],{"class":311},[301,2211,770],{"class":360},[301,2213,521],{"class":311},[301,2215,770],{"class":360},[301,2217,860],{"class":311},[301,2219,770],{"class":360},[301,2221,878],{"class":311},[301,2223,770],{"class":360},[301,2225,949],{"class":311},[301,2227,770],{"class":360},[301,2229,1389],{"class":311},[301,2231,770],{"class":360},[301,2233,2155],{"class":311},[301,2235,2236],{"class":360},"]].copy()\n",[301,2238,2239,2242,2244,2246,2248,2251,2253,2256,2259,2262,2265,2267,2270,2273,2275,2278,2280],{"class":303,"line":384},[301,2240,2241],{"class":360},"report[",[301,2243,1389],{"class":311},[301,2245,1259],{"class":360},[301,2247,410],{"class":356},[301,2249,2250],{"class":360}," report[",[301,2252,1389],{"class":311},[301,2254,2255],{"class":360},"].apply(",[301,2257,2258],{"class":356},"lambda",[301,2260,2261],{"class":360}," xs: ",[301,2263,2264],{"class":311},"\", \"",[301,2266,869],{"class":360},[301,2268,2269],{"class":561},"str",[301,2271,2272],{"class":360},"(x) ",[301,2274,1296],{"class":356},[301,2276,2277],{"class":360}," x ",[301,2279,436],{"class":356},[301,2281,2282],{"class":360}," xs))\n",[301,2284,2285],{"class":303,"line":391},[301,2286,388],{"emptyLinePlaceholder":387},[301,2288,2289,2292,2295,2298,2300,2302],{"class":303,"line":404},[301,2290,2291],{"class":356},"with",[301,2293,2294],{"class":360}," pd.ExcelWriter(",[301,2296,2297],{"class":311},"\"orders_audit.xlsx\"",[301,2299,1329],{"class":360},[301,2301,364],{"class":356},[301,2303,2304],{"class":360}," writer:\n",[301,2306,2307,2310,2313,2315,2318,2320,2322,2324,2326],{"class":303,"line":416},[301,2308,2309],{"class":360},"    report.to_excel(writer, ",[301,2311,2312],{"class":592},"sheet_name",[301,2314,410],{"class":356},[301,2316,2317],{"class":311},"\"Formula audit\"",[301,2319,770],{"class":360},[301,2321,593],{"class":592},[301,2323,410],{"class":356},[301,2325,598],{"class":561},[301,2327,555],{"class":360},[301,2329,2330,2332,2335,2337],{"class":303,"line":427},[301,2331,562],{"class":561},[301,2333,2334],{"class":360},"(pd.read_excel(",[301,2336,2297],{"class":311},[301,2338,2339],{"class":360},").shape)\n",[10,2341,2342,2343,2346],{},"In a real job, compute the ",[298,2344,2345],{},"inconsistent"," flag from Step 4 rather than listing cells by hand; it is spelled out here to keep the example short. Rerun the audit after the owner's fixes and the problem rows disappear from the table — a simple way to show the work is done.",[286,2348,2350],{"id":2349},"common-pitfalls","Common pitfalls",[2352,2353,2354,2365,2371,2377,2383],"ul",{},[2355,2356,2357,2364],"li",{},[2358,2359,2360,2361,2363],"strong",{},"Loading with ",[298,2362,335],{},"."," You get results, not formulas, and the audit sees nothing.",[2355,2366,2367,2370],{},[2358,2368,2369],{},"Regular expressions on formula text."," They break on nested functions and quoted sheet names; use the tokenizer.",[2355,2372,2373,2376],{},[2358,2374,2375],{},"Treating every constant as a problem."," Column indexes, zeros and ones are usually fine; review the rest.",[2355,2378,2379,2382],{},[2358,2380,2381],{},"Auditing only formula cells."," Typed values inside formula columns are invisible to an inventory of formulas.",[2355,2384,2385,2388,2389,2392,2393,2395],{},[2358,2386,2387],{},"Array and shared formulas."," Cells that hold part of an array formula can report a formula object rather than a string; convert with ",[298,2390,2391],{},"str()"," or read the formula's ",[298,2394,47],{}," before tokenising.",[286,2397,2399],{"id":2398},"performance-notes","Performance notes",[10,2401,2402],{},"Tokenising is the slowest step, at tens of thousands of formulas per second, which covers most business workbooks in a few seconds. For very large models, tokenise each distinct normalised pattern once — a column of 50,000 identical formulas has one pattern — and join the results back to the cells.",[286,2404,2406],{"id":2405},"conclusion","Conclusion",[10,2408,2409,2410,340,2412,2414,2415,2417],{},"An inventory of every formula, built from cells whose ",[298,2411,339],{},[298,2413,343],{},", becomes a full audit once each formula is tokenised: functions used, sheets and external files depended on, and numbers hard-coded inside. Normalising formulas to one anchor row with ",[298,2416,1476],{}," exposes the odd one out in a column, and checking formula columns for constants finds values typed over formulas. Write the findings to their own sheet in a copy, fix at the source, and rerun until the table is empty.",[286,2419,2421],{"id":2420},"frequently-asked-questions","Frequently asked questions",[10,2423,2424,2427],{},[2358,2425,2426],{},"How do I list every formula in a workbook?","\nLoad it with openpyxl without data_only, visit every cell on every sheet, and keep those whose data_type is \"f\" — their value is the formula text. Collect sheet, cell and formula into a DataFrame for analysis.",[10,2429,2430,2433],{},[2358,2431,2432],{},"How can I tell if one formula in a column differs from the rest?","\nTranslate every formula to the position of the first row with openpyxl's Translator. Formulas that follow the same pattern become identical text; the ones that do not stand out when you compare them with the most common version.",[10,2435,2436,2439,2440,2444],{},[2358,2437,2438],{},"What counts as a hard-coded number?","\nAny numeric constant inside a formula, such as the 1.2 in =B4",[2441,2442,2443],"em",{},"C4","1.2. Some are fine — a column index in VLOOKUP, a zero in a comparison — but rates, factors and thresholds belong in labelled input cells where they can be seen and changed.",[10,2446,2447,2450,2451,2453],{},[2358,2448,2449],{},"Can this find external workbook links?","\nYes. References to other files appear in the formula text with a bracketed index such as ",[301,2452,104],{},"Budget!B4. The tokenizer reports them as range operands, so the audit can flag any reference containing a bracket.",[286,2455,2457],{"id":2456},"related","Related",[2352,2459,2460,2466,2473,2480],{},[2355,2461,2462,2463,2465],{},"Up one level: ",[14,2464,17],{"href":16}," — writing, reading and recalculating formulas.",[2355,2467,2468,2472],{},[14,2469,2471],{"href":2470},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-formula-errors-in-a-workbook-with-python\u002F","Find #REF!, #DIV\u002F0! and Other Formula Errors in a Workbook"," — formulas that already fail.",[2355,2474,2475,2479],{},[14,2476,2478],{"href":2477},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Ffind-external-links-and-broken-references-in-a-workbook\u002F","Find External Links and Broken References in a Workbook with Python"," — the external links in depth.",[2355,2481,2482,2486],{},[14,2483,2485],{"href":2484},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fcopy-formula-down-a-column-in-openpyxl\u002F","Copy a Formula Down a Column in openpyxl"," — writing consistent columns in the first place.",[2488,2489,2490],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":296,"searchDepth":370,"depth":370,"links":2492},[2493,2494,2495,2496,2497,2498,2499,2500,2501,2502,2503,2504],{"id":288,"depth":370,"text":289},{"id":328,"depth":370,"text":329},{"id":607,"depth":370,"text":608},{"id":1174,"depth":370,"text":1175},{"id":1469,"depth":370,"text":1470},{"id":1754,"depth":370,"text":1755},{"id":2139,"depth":370,"text":2140},{"id":2349,"depth":370,"text":2350},{"id":2398,"depth":370,"text":2399},{"id":2405,"depth":370,"text":2406},{"id":2420,"depth":370,"text":2421},{"id":2456,"depth":370,"text":2457},"2026-09-25","Build an inventory of every formula in a workbook — which functions it uses, which sheets and external files it depends on, which numbers are hard-coded in it — then find the inconsistent formula in a column of identical ones and the value someone typed over a formula. Output the audit as its own sheet.","md",[2509,2511,2513,2515],{"q":2426,"a":2510},"Load it with openpyxl without data_only, visit every cell on every sheet, and keep those whose data_type is \"f\" — their value is the formula text. Collect sheet, cell and formula into a DataFrame for analysis.",{"q":2432,"a":2512},"Translate every formula to the position of the first row with openpyxl's Translator. Formulas that follow the same pattern become identical text; the ones that do not stand out when you compare them with the most common version.",{"q":2438,"a":2514},"Any numeric constant inside a formula, such as the 1.2 in =B4*C4*1.2. Some are fine — a column index in VLOOKUP, a zero in a comparison — but rates, factors and thresholds belong in labelled input cells where they can be seen and changed.",{"q":2449,"a":2516},"Yes. References to other files appear in the formula text with a bracketed index such as [1]Budget!B4. The tokenizer reports them as range operands, so the audit can flag any reference containing a bracket.","list every formula in a workbook for an audit",null,{"breadcrumb":2520},[2521,2524,2527],{"name":2522,"item":2523},"Home","\u002F",{"name":2525,"item":2526},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":17,"item":16},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Faudit-every-formula-in-a-workbook-with-python",{"title":2530,"description":2531},"Audit Excel Formulas with Python (Inventory, Inconsistencies, Hard-Coded Values)","List every formula in an Excel workbook with openpyxl, tokenise it to find functions, cross-sheet and external references and hard-coded constants, detect inconsistent formulas in a column and values typed over formulas.","audit-every-formula-in-a-workbook-with-python","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Faudit-every-formula-in-a-workbook-with-python\u002Findex",[2535,2536,2537],"excel-formulas","data-validation","openpyxl","how-to","6g9mHBfLQ3Jz9Mg2r8IHaSZIGqzAqJY6cbIMEN9J3Ts",[2541,2545],{"title":2542,"path":2543,"stem":2544,"children":-1},"Add SUM and AVERAGE Formulas with Python","\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fadd-sum-and-average-formulas-with-python","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fadd-sum-and-average-formulas-with-python\u002Findex",{"title":1980,"path":2546,"stem":2547,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fconvert-excel-formulas-to-values-with-python","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fconvert-excel-formulas-to-values-with-python\u002Findex",[2549,2551],{"path":2550,"title":2525},"\u002Fgetting-started-with-python-excel-automation",{"path":2552,"title":17},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python",[],[2555,2558,2562,2565,2568,2571],{"path":2556,"title":2557,"kind":2538},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fstore-formula-results-with-xlsxwriter","Store Formula Results with xlsxwriter So Every Viewer Shows Values",{"path":2559,"title":2560,"kind":2561},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-with-formula-errors-using-conditional-formatting","Highlight Cells with Formula Errors Using Conditional Formatting","fix",{"path":2563,"title":2564,"kind":2538},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel","Goal Seek in Python Instead of Excel",{"path":2566,"title":2567,"kind":2538},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcheck-that-totals-in-a-report-add-up","Check That Totals in a Report Add Up",{"path":2569,"title":2570,"kind":2538},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph","Call Excel Worksheet Functions Through Microsoft Graph",{"path":2572,"title":2573,"kind":2538},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frecalculate-excel-online-workbooks-with-graph-sessions","Recalculate Excel Online Workbooks with Graph Sessions",1790972075544]