[{"data":1,"prerenderedAt":2659},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-values-outside-a-tolerance-of-target-with-openpyxl":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-values-outside-a-tolerance-of-target-with-openpyxl":2626,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-values-outside-a-tolerance-of-target-with-openpyxl":2635,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-values-outside-a-tolerance-of-target-with-openpyxl":2640,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-values-outside-a-tolerance-of-target-with-openpyxl":2641},{"id":4,"title":5,"body":6,"dateModified":2593,"datePublished":2593,"description":2594,"extension":2595,"faq":2596,"keyword":2605,"kind":2606,"meta":2607,"navigation":127,"path":2616,"seo":2617,"slug":2619,"stem":2620,"topics":2621,"type":2624,"__hash__":2625},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-values-outside-a-tolerance-of-target-with-openpyxl\u002Findex.md","Highlight Values Outside a Tolerance of Target with openpyxl",{"type":7,"value":8,"toc":2582},"minimark",[9,24,29,58,65,69,72,666,681,685,692,1063,1080,1277,1281,1284,1483,1494,1579,1583,1586,2178,2185,2462,2466,2503,2507,2517,2521,2527,2533,2539,2545,2549,2578],[10,11,12,13,17,18,23],"p",{},"Many operational reports compare an actual figure with a target that differs row by row: each machine's output against its rated capacity, each cost centre's spend against its budget, each measurement against its specification. What the reader needs is not \"high\" or \"low\" but \"within tolerance or not\": a line 2% under target is fine, one 12% under is not. Conditional formatting rules that compare cells with ",[14,15,16],"em",{},"fixed"," thresholds cannot express that, but formula-based rules can, because each row's rule can look at that row's own target. This page builds a target report with openpyxl whose actual values turn green within tolerance, amber near the limit and red beyond it, with the tolerance held in a cell readers can change, an absolute-tolerance variant, per-row tolerances, safe handling of zero targets, and a recalculation check that the rules flag exactly the rows a pandas calculation says they should. It belongs to ",[19,20,22],"a",{"href":21},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002F","Applying Conditional Formatting with openpyxl",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52,55],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," openpyxl",[40,56,57],{"class":50}," pandas\n",[10,59,60,61,64],{},"LibreOffice (",[37,62,63],{},"soffice",") recalculates the workbook for the check in Step 4. The example is a production report: each line's output for the week against its target, with a default tolerance of 5%.",[25,66,68],{"id":67},"step-1-the-report-with-a-tolerance-setting","Step 1 — The report with a tolerance setting",[10,70,71],{},"Put the tolerance in a cell with a name, so the rules can refer to it and a reader can change it:",[30,73,77],{"className":74,"code":75,"language":76,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nfrom openpyxl import Workbook\nfrom openpyxl.workbook.defined_name import DefinedName\n\nlines = pd.DataFrame({\"Line\": [\"L1\", \"L2\", \"L3\", \"L4\", \"L5\", \"L6\", \"L7\", \"L8\"],\n                      \"Target\": [1200, 950, 1500, 800, 0, 1100, 640, 2000],\n                      \"Actual\": [1176, 1010, 1320, 812, 15, 1046, 702, 1990],\n                      \"Line tolerance\": [None, None, None, 0.02, None, None, 0.15, None]})\n\nwb = Workbook()\nws = wb.active\nws.title = \"Output\"\nws[\"A1\"], ws[\"B1\"] = \"Tolerance\", 0.05\nws[\"B1\"].number_format = \"0%\"\nwb.defined_names[\"Tolerance\"] = DefinedName(\"Tolerance\", attr_text=\"Output!$B$1\")\nws.append([])\nws.append(list(lines.columns) + [\"Variance %\"])\nfor r, row in enumerate(lines.itertuples(index=False), start=4):\n    ws.append([row.Line, row.Target, row.Actual, row._3])\n    ws[f\"E{r}\"] = f'=IF(B{r}=0,\"\",C{r}\u002FB{r}-1)'\n    ws[f\"E{r}\"].number_format = \"+0.0%;-0.0%;0.0%\"\n    ws[f\"D{r}\"].number_format = \"0%\"\nlast = ws.max_row\nprint(f\"data in rows 4–{last}; tolerance cell B1 named 'Tolerance'\")\n","python",[37,78,79,95,109,122,129,189,238,286,330,335,346,357,368,396,411,443,449,473,513,519,581,605,629,640],{"__ignoreMap":35},[40,80,81,85,89,92],{"class":42,"line":43},[40,82,84],{"class":83},"s-kum","import",[40,86,88],{"class":87},"skGVy"," pandas ",[40,90,91],{"class":83},"as",[40,93,94],{"class":87}," pd\n",[40,96,98,101,104,106],{"class":42,"line":97},2,[40,99,100],{"class":83},"from",[40,102,103],{"class":87}," openpyxl ",[40,105,84],{"class":83},[40,107,108],{"class":87}," Workbook\n",[40,110,112,114,117,119],{"class":42,"line":111},3,[40,113,100],{"class":83},[40,115,116],{"class":87}," openpyxl.workbook.defined_name ",[40,118,84],{"class":83},[40,120,121],{"class":87}," DefinedName\n",[40,123,125],{"class":42,"line":124},4,[40,126,128],{"emptyLinePlaceholder":127},true,"\n",[40,130,132,135,138,141,144,147,150,153,156,158,161,163,166,168,171,173,176,178,181,183,186],{"class":42,"line":131},5,[40,133,134],{"class":87},"lines ",[40,136,137],{"class":83},"=",[40,139,140],{"class":87}," pd.DataFrame({",[40,142,143],{"class":50},"\"Line\"",[40,145,146],{"class":87},": [",[40,148,149],{"class":50},"\"L1\"",[40,151,152],{"class":87},", ",[40,154,155],{"class":50},"\"L2\"",[40,157,152],{"class":87},[40,159,160],{"class":50},"\"L3\"",[40,162,152],{"class":87},[40,164,165],{"class":50},"\"L4\"",[40,167,152],{"class":87},[40,169,170],{"class":50},"\"L5\"",[40,172,152],{"class":87},[40,174,175],{"class":50},"\"L6\"",[40,177,152],{"class":87},[40,179,180],{"class":50},"\"L7\"",[40,182,152],{"class":87},[40,184,185],{"class":50},"\"L8\"",[40,187,188],{"class":87},"],\n",[40,190,192,195,197,201,203,206,208,211,213,216,218,221,223,226,228,231,233,236],{"class":42,"line":191},6,[40,193,194],{"class":50},"                      \"Target\"",[40,196,146],{"class":87},[40,198,200],{"class":199},"sP0c6","1200",[40,202,152],{"class":87},[40,204,205],{"class":199},"950",[40,207,152],{"class":87},[40,209,210],{"class":199},"1500",[40,212,152],{"class":87},[40,214,215],{"class":199},"800",[40,217,152],{"class":87},[40,219,220],{"class":199},"0",[40,222,152],{"class":87},[40,224,225],{"class":199},"1100",[40,227,152],{"class":87},[40,229,230],{"class":199},"640",[40,232,152],{"class":87},[40,234,235],{"class":199},"2000",[40,237,188],{"class":87},[40,239,241,244,246,249,251,254,256,259,261,264,266,269,271,274,276,279,281,284],{"class":42,"line":240},7,[40,242,243],{"class":50},"                      \"Actual\"",[40,245,146],{"class":87},[40,247,248],{"class":199},"1176",[40,250,152],{"class":87},[40,252,253],{"class":199},"1010",[40,255,152],{"class":87},[40,257,258],{"class":199},"1320",[40,260,152],{"class":87},[40,262,263],{"class":199},"812",[40,265,152],{"class":87},[40,267,268],{"class":199},"15",[40,270,152],{"class":87},[40,272,273],{"class":199},"1046",[40,275,152],{"class":87},[40,277,278],{"class":199},"702",[40,280,152],{"class":87},[40,282,283],{"class":199},"1990",[40,285,188],{"class":87},[40,287,289,292,294,297,299,301,303,305,307,310,312,314,316,318,320,323,325,327],{"class":42,"line":288},8,[40,290,291],{"class":50},"                      \"Line tolerance\"",[40,293,146],{"class":87},[40,295,296],{"class":199},"None",[40,298,152],{"class":87},[40,300,296],{"class":199},[40,302,152],{"class":87},[40,304,296],{"class":199},[40,306,152],{"class":87},[40,308,309],{"class":199},"0.02",[40,311,152],{"class":87},[40,313,296],{"class":199},[40,315,152],{"class":87},[40,317,296],{"class":199},[40,319,152],{"class":87},[40,321,322],{"class":199},"0.15",[40,324,152],{"class":87},[40,326,296],{"class":199},[40,328,329],{"class":87},"]})\n",[40,331,333],{"class":42,"line":332},9,[40,334,128],{"emptyLinePlaceholder":127},[40,336,338,341,343],{"class":42,"line":337},10,[40,339,340],{"class":87},"wb ",[40,342,137],{"class":83},[40,344,345],{"class":87}," Workbook()\n",[40,347,349,352,354],{"class":42,"line":348},11,[40,350,351],{"class":87},"ws ",[40,353,137],{"class":83},[40,355,356],{"class":87}," wb.active\n",[40,358,360,363,365],{"class":42,"line":359},12,[40,361,362],{"class":87},"ws.title ",[40,364,137],{"class":83},[40,366,367],{"class":50}," \"Output\"\n",[40,369,371,374,377,380,383,386,388,391,393],{"class":42,"line":370},13,[40,372,373],{"class":87},"ws[",[40,375,376],{"class":50},"\"A1\"",[40,378,379],{"class":87},"], ws[",[40,381,382],{"class":50},"\"B1\"",[40,384,385],{"class":87},"] ",[40,387,137],{"class":83},[40,389,390],{"class":50}," \"Tolerance\"",[40,392,152],{"class":87},[40,394,395],{"class":199},"0.05\n",[40,397,399,401,403,406,408],{"class":42,"line":398},14,[40,400,373],{"class":87},[40,402,382],{"class":50},[40,404,405],{"class":87},"].number_format ",[40,407,137],{"class":83},[40,409,410],{"class":50}," \"0%\"\n",[40,412,414,417,420,422,424,427,429,431,435,437,440],{"class":42,"line":413},15,[40,415,416],{"class":87},"wb.defined_names[",[40,418,419],{"class":50},"\"Tolerance\"",[40,421,385],{"class":87},[40,423,137],{"class":83},[40,425,426],{"class":87}," DefinedName(",[40,428,419],{"class":50},[40,430,152],{"class":87},[40,432,434],{"class":433},"sa561","attr_text",[40,436,137],{"class":83},[40,438,439],{"class":50},"\"Output!$B$1\"",[40,441,442],{"class":87},")\n",[40,444,446],{"class":42,"line":445},16,[40,447,448],{"class":87},"ws.append([])\n",[40,450,452,455,458,461,464,467,470],{"class":42,"line":451},17,[40,453,454],{"class":87},"ws.append(",[40,456,457],{"class":199},"list",[40,459,460],{"class":87},"(lines.columns) ",[40,462,463],{"class":83},"+",[40,465,466],{"class":87}," [",[40,468,469],{"class":50},"\"Variance %\"",[40,471,472],{"class":87},"])\n",[40,474,476,479,482,485,488,491,494,496,499,502,505,507,510],{"class":42,"line":475},18,[40,477,478],{"class":83},"for",[40,480,481],{"class":87}," r, row ",[40,483,484],{"class":83},"in",[40,486,487],{"class":199}," enumerate",[40,489,490],{"class":87},"(lines.itertuples(",[40,492,493],{"class":433},"index",[40,495,137],{"class":83},[40,497,498],{"class":199},"False",[40,500,501],{"class":87},"), ",[40,503,504],{"class":433},"start",[40,506,137],{"class":83},[40,508,509],{"class":199},"4",[40,511,512],{"class":87},"):\n",[40,514,516],{"class":42,"line":515},19,[40,517,518],{"class":87},"    ws.append([row.Line, row.Target, row.Actual, row._3])\n",[40,520,522,525,528,531,535,538,541,544,546,548,551,554,556,558,560,563,565,567,569,572,574,576,578],{"class":42,"line":521},20,[40,523,524],{"class":87},"    ws[",[40,526,527],{"class":83},"f",[40,529,530],{"class":50},"\"E",[40,532,534],{"class":533},"sSjpA","{",[40,536,537],{"class":87},"r",[40,539,540],{"class":533},"}",[40,542,543],{"class":50},"\"",[40,545,385],{"class":87},[40,547,137],{"class":83},[40,549,550],{"class":83}," f",[40,552,553],{"class":50},"'=IF(B",[40,555,534],{"class":533},[40,557,537],{"class":87},[40,559,540],{"class":533},[40,561,562],{"class":50},"=0,\"\",C",[40,564,534],{"class":533},[40,566,537],{"class":87},[40,568,540],{"class":533},[40,570,571],{"class":50},"\u002FB",[40,573,534],{"class":533},[40,575,537],{"class":87},[40,577,540],{"class":533},[40,579,580],{"class":50},"-1)'\n",[40,582,584,586,588,590,592,594,596,598,600,602],{"class":42,"line":583},21,[40,585,524],{"class":87},[40,587,527],{"class":83},[40,589,530],{"class":50},[40,591,534],{"class":533},[40,593,537],{"class":87},[40,595,540],{"class":533},[40,597,543],{"class":50},[40,599,405],{"class":87},[40,601,137],{"class":83},[40,603,604],{"class":50}," \"+0.0%;-0.0%;0.0%\"\n",[40,606,608,610,612,615,617,619,621,623,625,627],{"class":42,"line":607},22,[40,609,524],{"class":87},[40,611,527],{"class":83},[40,613,614],{"class":50},"\"D",[40,616,534],{"class":533},[40,618,537],{"class":87},[40,620,540],{"class":533},[40,622,543],{"class":50},[40,624,405],{"class":87},[40,626,137],{"class":83},[40,628,410],{"class":50},[40,630,632,635,637],{"class":42,"line":631},23,[40,633,634],{"class":87},"last ",[40,636,137],{"class":83},[40,638,639],{"class":87}," ws.max_row\n",[40,641,643,646,649,651,654,656,659,661,664],{"class":42,"line":642},24,[40,644,645],{"class":199},"print",[40,647,648],{"class":87},"(",[40,650,527],{"class":83},[40,652,653],{"class":50},"\"data in rows 4–",[40,655,534],{"class":533},[40,657,658],{"class":87},"last",[40,660,540],{"class":533},[40,662,663],{"class":50},"; tolerance cell B1 named 'Tolerance'\"",[40,665,442],{"class":87},[10,667,668,669,672,673,676,677,680],{},"The tolerance lives in B1 and is named ",[37,670,671],{},"Tolerance",", so rules can use the name rather than ",[37,674,675],{},"$B$1"," and keep working if someone inserts a row above the table. A variance column shows each line's percentage difference from target, guarded so a zero target gives an empty cell rather than ",[37,678,679],{},"#DIV\u002F0!",". Two lines carry their own tolerance in column D — L4 is a precision line held to 2%, L7 a new line allowed 15% — and the rest fall back to the sheet-wide 5%.",[25,682,684],{"id":683},"step-2-the-tolerance-rules","Step 2 — The tolerance rules",[10,686,687,688,691],{},"Each rule is a formula for the first data row; Excel shifts it down for every other row. The ",[14,689,690],{},"effective"," tolerance is the row's own value if it has one, otherwise the named default:",[30,693,695],{"className":74,"code":694,"language":76,"meta":35,"style":35},"from openpyxl.formatting.rule import FormulaRule\nfrom openpyxl.styles import Font, PatternFill\n\nTOL = \"IF(ISNUMBER($D4),$D4,Tolerance)\"\nDEV = \"ABS($C4-$B4)\u002F$B4\"\nrules = [\n    (\"zero target\", f\"AND($B4=0,$C4\u003C>0)\", \"D9D9D9\", \"404040\"),\n    (\"outside\", f\"AND($B4\u003C>0,{DEV}>{TOL})\", \"FFC7CE\", \"9C0006\"),\n    (\"near limit\", f\"AND($B4\u003C>0,{DEV}>{TOL}*0.6)\", \"FFEB9C\", \"7F6000\"),\n    (\"within\", f\"AND($B4\u003C>0,{DEV}\u003C={TOL}*0.6)\", \"C6EFCE\", \"006100\"),\n]\narea = f\"C4:C{last}\"\nfor name, formula, fill, font in rules:\n    ws.conditional_formatting.add(area, FormulaRule(formula=[formula], fill=PatternFill(\"solid\", fgColor=fill),\n                                                    font=Font(color=font), stopIfTrue=True))\nprint(\"\\n\".join(f\"{n:\u003C12} {f}\" for n, f, _, _ in rules))\n",[37,696,697,709,721,725,736,746,756,784,828,870,912,917,938,950,984,1013],{"__ignoreMap":35},[40,698,699,701,704,706],{"class":42,"line":43},[40,700,100],{"class":83},[40,702,703],{"class":87}," openpyxl.formatting.rule ",[40,705,84],{"class":83},[40,707,708],{"class":87}," FormulaRule\n",[40,710,711,713,716,718],{"class":42,"line":97},[40,712,100],{"class":83},[40,714,715],{"class":87}," openpyxl.styles ",[40,717,84],{"class":83},[40,719,720],{"class":87}," Font, PatternFill\n",[40,722,723],{"class":42,"line":111},[40,724,128],{"emptyLinePlaceholder":127},[40,726,727,730,733],{"class":42,"line":124},[40,728,729],{"class":199},"TOL",[40,731,732],{"class":83}," =",[40,734,735],{"class":50}," \"IF(ISNUMBER($D4),$D4,Tolerance)\"\n",[40,737,738,741,743],{"class":42,"line":131},[40,739,740],{"class":199},"DEV",[40,742,732],{"class":83},[40,744,745],{"class":50}," \"ABS($C4-$B4)\u002F$B4\"\n",[40,747,748,751,753],{"class":42,"line":191},[40,749,750],{"class":87},"rules ",[40,752,137],{"class":83},[40,754,755],{"class":87}," [\n",[40,757,758,761,764,766,768,771,773,776,778,781],{"class":42,"line":240},[40,759,760],{"class":87},"    (",[40,762,763],{"class":50},"\"zero target\"",[40,765,152],{"class":87},[40,767,527],{"class":83},[40,769,770],{"class":50},"\"AND($B4=0,$C4\u003C>0)\"",[40,772,152],{"class":87},[40,774,775],{"class":50},"\"D9D9D9\"",[40,777,152],{"class":87},[40,779,780],{"class":50},"\"404040\"",[40,782,783],{"class":87},"),\n",[40,785,786,788,791,793,795,798,800,802,804,807,809,811,813,816,818,821,823,826],{"class":42,"line":288},[40,787,760],{"class":87},[40,789,790],{"class":50},"\"outside\"",[40,792,152],{"class":87},[40,794,527],{"class":83},[40,796,797],{"class":50},"\"AND($B4\u003C>0,",[40,799,534],{"class":533},[40,801,740],{"class":199},[40,803,540],{"class":533},[40,805,806],{"class":50},">",[40,808,534],{"class":533},[40,810,729],{"class":199},[40,812,540],{"class":533},[40,814,815],{"class":50},")\"",[40,817,152],{"class":87},[40,819,820],{"class":50},"\"FFC7CE\"",[40,822,152],{"class":87},[40,824,825],{"class":50},"\"9C0006\"",[40,827,783],{"class":87},[40,829,830,832,835,837,839,841,843,845,847,849,851,853,855,858,860,863,865,868],{"class":42,"line":332},[40,831,760],{"class":87},[40,833,834],{"class":50},"\"near limit\"",[40,836,152],{"class":87},[40,838,527],{"class":83},[40,840,797],{"class":50},[40,842,534],{"class":533},[40,844,740],{"class":199},[40,846,540],{"class":533},[40,848,806],{"class":50},[40,850,534],{"class":533},[40,852,729],{"class":199},[40,854,540],{"class":533},[40,856,857],{"class":50},"*0.6)\"",[40,859,152],{"class":87},[40,861,862],{"class":50},"\"FFEB9C\"",[40,864,152],{"class":87},[40,866,867],{"class":50},"\"7F6000\"",[40,869,783],{"class":87},[40,871,872,874,877,879,881,883,885,887,889,892,894,896,898,900,902,905,907,910],{"class":42,"line":337},[40,873,760],{"class":87},[40,875,876],{"class":50},"\"within\"",[40,878,152],{"class":87},[40,880,527],{"class":83},[40,882,797],{"class":50},[40,884,534],{"class":533},[40,886,740],{"class":199},[40,888,540],{"class":533},[40,890,891],{"class":50},"\u003C=",[40,893,534],{"class":533},[40,895,729],{"class":199},[40,897,540],{"class":533},[40,899,857],{"class":50},[40,901,152],{"class":87},[40,903,904],{"class":50},"\"C6EFCE\"",[40,906,152],{"class":87},[40,908,909],{"class":50},"\"006100\"",[40,911,783],{"class":87},[40,913,914],{"class":42,"line":348},[40,915,916],{"class":87},"]\n",[40,918,919,922,924,926,929,931,933,935],{"class":42,"line":359},[40,920,921],{"class":87},"area ",[40,923,137],{"class":83},[40,925,550],{"class":83},[40,927,928],{"class":50},"\"C4:C",[40,930,534],{"class":533},[40,932,658],{"class":87},[40,934,540],{"class":533},[40,936,937],{"class":50},"\"\n",[40,939,940,942,945,947],{"class":42,"line":370},[40,941,478],{"class":83},[40,943,944],{"class":87}," name, formula, fill, font ",[40,946,484],{"class":83},[40,948,949],{"class":87}," rules:\n",[40,951,952,955,958,960,963,966,968,971,974,976,979,981],{"class":42,"line":398},[40,953,954],{"class":87},"    ws.conditional_formatting.add(area, FormulaRule(",[40,956,957],{"class":433},"formula",[40,959,137],{"class":83},[40,961,962],{"class":87},"[formula], ",[40,964,965],{"class":433},"fill",[40,967,137],{"class":83},[40,969,970],{"class":87},"PatternFill(",[40,972,973],{"class":50},"\"solid\"",[40,975,152],{"class":87},[40,977,978],{"class":433},"fgColor",[40,980,137],{"class":83},[40,982,983],{"class":87},"fill),\n",[40,985,986,989,991,994,997,999,1002,1005,1007,1010],{"class":42,"line":413},[40,987,988],{"class":433},"                                                    font",[40,990,137],{"class":83},[40,992,993],{"class":87},"Font(",[40,995,996],{"class":433},"color",[40,998,137],{"class":83},[40,1000,1001],{"class":87},"font), ",[40,1003,1004],{"class":433},"stopIfTrue",[40,1006,137],{"class":83},[40,1008,1009],{"class":199},"True",[40,1011,1012],{"class":87},"))\n",[40,1014,1015,1017,1019,1021,1024,1026,1029,1031,1033,1035,1038,1041,1043,1046,1048,1050,1052,1055,1058,1060],{"class":42,"line":445},[40,1016,645],{"class":199},[40,1018,648],{"class":87},[40,1020,543],{"class":50},[40,1022,1023],{"class":533},"\\n",[40,1025,543],{"class":50},[40,1027,1028],{"class":87},".join(",[40,1030,527],{"class":83},[40,1032,543],{"class":50},[40,1034,534],{"class":533},[40,1036,1037],{"class":87},"n",[40,1039,1040],{"class":83},":\u003C12",[40,1042,540],{"class":533},[40,1044,1045],{"class":533}," {",[40,1047,527],{"class":87},[40,1049,540],{"class":533},[40,1051,543],{"class":50},[40,1053,1054],{"class":83}," for",[40,1056,1057],{"class":87}," n, f, _, _ ",[40,1059,484],{"class":83},[40,1061,1062],{"class":87}," rules))\n",[10,1064,1065,1066,1068,1069,1072,1073,1075,1076,23],{},"The rules are evaluated in order, and ",[37,1067,1004],{}," makes the first match win, so they can be written from the most specific to the least. A zero target gets its own grey rule — any output on a line that was meant to be idle is worth a look, and a percentage deviation from zero is meaningless. Outside tolerance is red. The amber band starts at 60% of the tolerance, warning that a line is drifting before it fails; within that, green. Columns are locked with ",[37,1070,1071],{},"$"," and rows left relative, so every row compares its own actual, target and tolerance. The rule order and ",[37,1074,1004],{}," behaviour are covered in ",[19,1077,1079],{"href":1078},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fcontrol-conditional-formatting-rule-order-and-stop-if-true\u002F","Control Conditional Formatting Rule Order and Stop If True",[1081,1082,1090,1091,1090,1095,1090,1099,1090,1105,1090,1115,1090,1126,1090,1132,1090,1136,1090,1140,1090,1143,1090,1148,1090,1157,1090,1162,1090,1169,1090,1176,1090,1181,1090,1186,1090,1189,1090,1192,1090,1196,1090,1199,1090,1202,1090,1208,1090,1211,1090,1214,1090,1217,1090,1220,1090,1224,1090,1227,1090,1232,1090,1239,1090,1243,1090,1246,1090,1248,1090,1251,1090,1255,1090,1258,1090,1263,1090,1270,1090,1274],"svg",{"viewBox":1083,"role":1084,"ariaLabelledBy":1085,"xmlns":1088,"style":1089},"4 0 752 272","img",[1086,1087],"tol-bands-t","tol-bands-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[1092,1093,1094],"title",{"id":1086},"The colour each actual value gets",[1096,1097,1098],"desc",{"id":1087},"A non-zero actual against a zero target is grey. Otherwise the deviation from target is compared with the row's tolerance, or the sheet default of five percent. Beyond the tolerance is red, between sixty and one hundred percent of it is amber, and closer than that is green.",[1100,1101],"rect",{"x":509,"y":220,"width":1102,"height":1103,"fill":1104},"752","272","#ffffff",[1100,1106],{"x":1107,"y":1108,"width":1109,"height":1110,"rx":1111,"fill":1112,"stroke":1113,"style":1114},"240.0","16.0","280.0","75.5","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[1116,1117,1119],"g",{"transform":1118},"translate(368.0,29.0) scale(1.000)",[1120,1121],"path",{"d":1122,"fill":1123,"stroke":1124,"style":1125},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[1127,1128,1131],"text",{"x":1129,"y":1110,"style":1130},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Deviation from this row's target",[42,1133],{"x1":1129,"y1":1134,"x2":1129,"y2":1135,"stroke":1113,"style":1114},"91.5","111.5",[42,1137],{"x1":1138,"y1":1135,"x2":1139,"y2":1135,"stroke":1113,"style":1114},"104.0","656.0",[42,1141],{"x1":1138,"y1":1135,"x2":1138,"y2":1142,"stroke":1113,"style":1114},"157.5",[1144,1145],"polygon",{"points":1146,"fill":1147},"104.0,161.5 99.0,152.5 109.0,152.5","#5b5cf0",[1100,1149],{"x":1150,"y":1151,"width":1152,"height":1153,"rx":1154,"fill":1104,"stroke":1155,"style":1156},"65.3","121.5","77.5","21.0","10","var(--line,#cdd5e6)","stroke-width:1.2px",[1127,1158,1161],{"x":1138,"y":1159,"style":1160},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","target is 0",[1100,1163],{"x":1164,"y":1165,"width":1166,"height":1167,"rx":1111,"fill":1168,"stroke":1155,"style":1114},"20.0","163.5","168.0","93.0","#e7ebef",[1116,1170,1172],{"transform":1171},"translate(92.0,176.5) scale(1.000)",[1120,1173],{"d":1174,"fill":1123,"stroke":1175,"style":1125},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","var(--text,#172033)",[1127,1177,1180],{"x":1138,"y":1178,"style":1179},"223.0","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","grey",[1127,1182,1185],{"x":1138,"y":1183,"style":1184},"242.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","check the line",[42,1187],{"x1":1188,"y1":1135,"x2":1188,"y2":1142,"stroke":1113,"style":1114},"288.0",[1144,1190],{"points":1191,"fill":1147},"288.0,161.5 283.0,152.5 293.0,152.5",[1100,1193],{"x":1194,"y":1151,"width":1195,"height":1153,"rx":1154,"fill":1104,"stroke":1155,"style":1156},"245.4","85.1",[1127,1197,1198],{"x":1188,"y":1159,"style":1160},"> tolerance",[1100,1200],{"x":1201,"y":1165,"width":1166,"height":1167,"rx":1111,"fill":1112,"stroke":1113,"style":1114},"204.0",[1116,1203,1205],{"transform":1204},"translate(276.0,176.5) scale(1.000)",[1120,1206],{"d":1207,"fill":1123,"stroke":1124,"style":1125},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[1127,1209,1210],{"x":1188,"y":1178,"style":1130},"red",[1127,1212,1213],{"x":1188,"y":1183,"style":1184},"outside tolerance",[42,1215],{"x1":1216,"y1":1135,"x2":1216,"y2":1142,"stroke":1113,"style":1114},"472.0",[1144,1218],{"points":1219,"fill":1147},"472.0,161.5 467.0,152.5 477.0,152.5",[1100,1221],{"x":1222,"y":1151,"width":1223,"height":1153,"rx":1154,"fill":1104,"stroke":1155,"style":1156},"422.3","99.4",[1127,1225,1226],{"x":1216,"y":1159,"style":1160},"60–100% of it",[1100,1228],{"x":1229,"y":1165,"width":1166,"height":1167,"rx":1111,"fill":1230,"stroke":1231,"style":1114},"388.0","#fdefd8","var(--gold,#b4740a)",[1116,1233,1235],{"transform":1234},"translate(460.0,176.5) scale(1.000)",[1120,1236],{"d":1237,"fill":1123,"stroke":1238,"style":1125},"M12 3a9 9 0 1 0 .01 0z M12 7v5l3.5 2","var(--gold-ink,#7a4e06)",[1127,1240,1242],{"x":1216,"y":1178,"style":1241},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","amber",[1127,1244,1245],{"x":1216,"y":1183,"style":1184},"drifting",[42,1247],{"x1":1139,"y1":1135,"x2":1139,"y2":1142,"stroke":1113,"style":1114},[1144,1249],{"points":1250,"fill":1147},"656.0,161.5 651.0,152.5 661.0,152.5",[1100,1252],{"x":1253,"y":1151,"width":1254,"height":1153,"rx":1154,"fill":1104,"stroke":1155,"style":1156},"614.5","82.9",[1127,1256,1257],{"x":1139,"y":1159,"style":1160},"\u003C 60% of it",[1100,1259],{"x":1260,"y":1165,"width":1166,"height":1167,"rx":1111,"fill":1261,"stroke":1262,"style":1114},"572.0","#d9f4f1","var(--teal,#0f9488)",[1116,1264,1266],{"transform":1265},"translate(644.0,176.5) scale(1.000)",[1120,1267],{"d":1268,"fill":1123,"stroke":1269,"style":1125},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[1127,1271,1273],{"x":1139,"y":1178,"style":1272},"font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","green",[1127,1275,1276],{"x":1139,"y":1183,"style":1184},"within tolerance",[25,1278,1280],{"id":1279},"step-3-an-absolute-tolerance-instead","Step 3 — An absolute tolerance instead",[10,1282,1283],{},"Percentages are right for most targets, but not for small ones: 5% of a target of 40 is two units, which may be within measurement noise. An absolute tolerance — \"within 25 units\" — is a different expression of the same idea. Add it as a second sheet so both can be compared:",[30,1285,1287],{"className":74,"code":1286,"language":76,"meta":35,"style":35},"abs_ws = wb.create_sheet(\"Absolute\")\nabs_ws[\"A1\"], abs_ws[\"B1\"] = \"Tolerance (units)\", 25\nwb.defined_names[\"UnitTolerance\"] = DefinedName(\"UnitTolerance\", attr_text=\"Absolute!$B$1\")\nabs_ws.append([])\nabs_ws.append([\"Line\", \"Target\", \"Actual\"])\nfor row in lines.itertuples(index=False):\n    abs_ws.append([row.Line, row.Target, row.Actual])\nabs_ws.conditional_formatting.add(f\"C4:C{abs_ws.max_row}\", FormulaRule(formula=[\"ABS($C4-$B4)>UnitTolerance\"],\n                                  fill=PatternFill(\"solid\", fgColor=\"FFC7CE\"), font=Font(color=\"9C0006\")))\nwb.save(\"output_tolerance.xlsx\")\n",[37,1288,1289,1304,1328,1354,1359,1378,1398,1403,1436,1473],{"__ignoreMap":35},[40,1290,1291,1294,1296,1299,1302],{"class":42,"line":43},[40,1292,1293],{"class":87},"abs_ws ",[40,1295,137],{"class":83},[40,1297,1298],{"class":87}," wb.create_sheet(",[40,1300,1301],{"class":50},"\"Absolute\"",[40,1303,442],{"class":87},[40,1305,1306,1309,1311,1314,1316,1318,1320,1323,1325],{"class":42,"line":97},[40,1307,1308],{"class":87},"abs_ws[",[40,1310,376],{"class":50},[40,1312,1313],{"class":87},"], abs_ws[",[40,1315,382],{"class":50},[40,1317,385],{"class":87},[40,1319,137],{"class":83},[40,1321,1322],{"class":50}," \"Tolerance (units)\"",[40,1324,152],{"class":87},[40,1326,1327],{"class":199},"25\n",[40,1329,1330,1332,1335,1337,1339,1341,1343,1345,1347,1349,1352],{"class":42,"line":111},[40,1331,416],{"class":87},[40,1333,1334],{"class":50},"\"UnitTolerance\"",[40,1336,385],{"class":87},[40,1338,137],{"class":83},[40,1340,426],{"class":87},[40,1342,1334],{"class":50},[40,1344,152],{"class":87},[40,1346,434],{"class":433},[40,1348,137],{"class":83},[40,1350,1351],{"class":50},"\"Absolute!$B$1\"",[40,1353,442],{"class":87},[40,1355,1356],{"class":42,"line":124},[40,1357,1358],{"class":87},"abs_ws.append([])\n",[40,1360,1361,1364,1366,1368,1371,1373,1376],{"class":42,"line":131},[40,1362,1363],{"class":87},"abs_ws.append([",[40,1365,143],{"class":50},[40,1367,152],{"class":87},[40,1369,1370],{"class":50},"\"Target\"",[40,1372,152],{"class":87},[40,1374,1375],{"class":50},"\"Actual\"",[40,1377,472],{"class":87},[40,1379,1380,1382,1385,1387,1390,1392,1394,1396],{"class":42,"line":191},[40,1381,478],{"class":83},[40,1383,1384],{"class":87}," row ",[40,1386,484],{"class":83},[40,1388,1389],{"class":87}," lines.itertuples(",[40,1391,493],{"class":433},[40,1393,137],{"class":83},[40,1395,498],{"class":199},[40,1397,512],{"class":87},[40,1399,1400],{"class":42,"line":240},[40,1401,1402],{"class":87},"    abs_ws.append([row.Line, row.Target, row.Actual])\n",[40,1404,1405,1408,1410,1412,1414,1417,1419,1421,1424,1426,1428,1431,1434],{"class":42,"line":288},[40,1406,1407],{"class":87},"abs_ws.conditional_formatting.add(",[40,1409,527],{"class":83},[40,1411,928],{"class":50},[40,1413,534],{"class":533},[40,1415,1416],{"class":87},"abs_ws.max_row",[40,1418,540],{"class":533},[40,1420,543],{"class":50},[40,1422,1423],{"class":87},", FormulaRule(",[40,1425,957],{"class":433},[40,1427,137],{"class":83},[40,1429,1430],{"class":87},"[",[40,1432,1433],{"class":50},"\"ABS($C4-$B4)>UnitTolerance\"",[40,1435,188],{"class":87},[40,1437,1438,1441,1443,1445,1447,1449,1451,1453,1455,1457,1460,1462,1464,1466,1468,1470],{"class":42,"line":332},[40,1439,1440],{"class":433},"                                  fill",[40,1442,137],{"class":83},[40,1444,970],{"class":87},[40,1446,973],{"class":50},[40,1448,152],{"class":87},[40,1450,978],{"class":433},[40,1452,137],{"class":83},[40,1454,820],{"class":50},[40,1456,501],{"class":87},[40,1458,1459],{"class":433},"font",[40,1461,137],{"class":83},[40,1463,993],{"class":87},[40,1465,996],{"class":433},[40,1467,137],{"class":83},[40,1469,825],{"class":50},[40,1471,1472],{"class":87},")))\n",[40,1474,1475,1478,1481],{"class":42,"line":337},[40,1476,1477],{"class":87},"wb.save(",[40,1479,1480],{"class":50},"\"output_tolerance.xlsx\"",[40,1482,442],{"class":87},[10,1484,1485,1486,1489,1490,23],{},"Here a line is flagged when it misses its target by more than 25 units, whatever the target's size, so L1's shortfall of 24 is fine while L2's overshoot of 60 is flagged, and the zero-target line L5 needs no special case because no division is involved. In practice the two are often combined — \"within 5% or 25 units, whichever is larger\" — which is one ",[37,1487,1488],{},"MAX()"," inside the rule. Comparing values against fixed thresholds instead of per-row targets is covered in ",[19,1491,1493],{"href":1492},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-above-a-threshold-with-openpyxl\u002F","Highlight Cells Above a Threshold with openpyxl",[1081,1495,1090,1500,1090,1503,1090,1506,1090,1509,1090,1514,1090,1521,1090,1527,1090,1533,1090,1538,1090,1542,1090,1546,1090,1550,1090,1553,1090,1558,1090,1563,1090,1567,1090,1570,1090,1573,1090,1576],{"viewBox":1496,"role":1084,"ariaLabelledBy":1497,"xmlns":1088,"style":1089},"4 0 752 233",[1498,1499],"tol-kinds-t","tol-kinds-d",[1092,1501,1502],{"id":1498},"Percentage or absolute tolerance",[1096,1504,1505],{"id":1499},"A percentage tolerance scales with the target, so it suits targets of very different sizes but needs a guard for zero targets and is too strict for small ones. An absolute tolerance is the same number of units for every row, so it suits measurements with a fixed precision.",[1100,1507],{"x":509,"y":220,"width":1102,"height":1508,"fill":1104},"233",[1100,1510],{"x":1164,"y":1108,"width":1511,"height":1512,"rx":1513,"fill":1112,"stroke":1113,"style":1114},"351.0","201.0","14",[1116,1515,1517],{"transform":1516},"translate(182.5,29.0) scale(1.083)",[1120,1518],{"d":1519,"fill":1123,"stroke":1124,"style":1520},"M4 20h16 M4 20V4 M6 16l4-5 4 3 6-7","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[1127,1522,1526],{"x":1523,"y":1524,"style":1525},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Percentage (5%)",[42,1528],{"x1":1529,"y1":1530,"x2":1531,"y2":1530,"stroke":1113,"style":1532},"36.0","91.0","355.0","stroke-width:1px",[1127,1534,1537],{"x":1523,"y":1535,"style":1536},"111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","scales with the target",[1127,1539,1541],{"x":1523,"y":1540,"style":1536},"137.0","needs a zero-target guard",[1127,1543,1545],{"x":1523,"y":1544,"style":1536},"163.0","strict on small targets",[1127,1547,1549],{"x":1523,"y":1548,"style":1536},"189.0","typical for output and budgets",[1100,1551],{"x":1552,"y":1108,"width":1511,"height":1512,"rx":1513,"fill":1261,"stroke":1262,"style":1114},"389.0",[1116,1554,1556],{"transform":1555},"translate(551.5,29.0) scale(1.083)",[1120,1557],{"d":1122,"fill":1123,"stroke":1269,"style":1520},[1127,1559,1562],{"x":1560,"y":1524,"style":1561},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Absolute (25 units)",[42,1564],{"x1":1565,"y1":1530,"x2":1566,"y2":1530,"stroke":1262,"style":1532},"405.0","724.0",[1127,1568,1569],{"x":1560,"y":1535,"style":1536},"same limit for every row",[1127,1571,1572],{"x":1560,"y":1540,"style":1536},"no division needed",[1127,1574,1575],{"x":1560,"y":1544,"style":1536},"lenient on large targets",[1127,1577,1578],{"x":1560,"y":1548,"style":1536},"typical for measurements",[25,1580,1582],{"id":1581},"step-4-check-the-rules-against-pandas","Step 4 — Check the rules against pandas",[10,1584,1585],{},"Rules that nobody can test tend to be subtly wrong. Write the same classification as a hidden helper formula, recalculate, and compare with pandas:",[30,1587,1589],{"className":74,"code":1588,"language":76,"meta":35,"style":35},"import subprocess\nfrom openpyxl import load_workbook\n\nwb = load_workbook(\"output_tolerance.xlsx\")\nws = wb[\"Output\"]\nfor r in range(4, ws.max_row + 1):\n    tol, dev = TOL.replace(\"4\", str(r)), DEV.replace(\"4\", str(r))\n    ws[f\"G{r}\"] = (f'=IF(AND($B{r}=0,$C{r}\u003C>0),\"grey\",IF($B{r}=0,\"\",IF({dev}>{tol},\"red\",'\n                   f'IF({dev}>{tol}*0.6,\"amber\",\"green\"))))')\nws.column_dimensions[\"G\"].hidden = True\nwb.save(\"output_tolerance.xlsx\")\nsubprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"xlsx\", \"--outdir\", \"calc\", \"output_tolerance.xlsx\"], check=True, capture_output=True)\nexcel = [c.value for c in load_workbook(\"calc\u002Foutput_tolerance.xlsx\", data_only=True)[\"Output\"][\"G\"][3:]]\n\ntol = lines[\"Line tolerance\"].fillna(0.05)\ndev = (lines[\"Actual\"] - lines[\"Target\"]).abs() \u002F lines[\"Target\"].where(lines[\"Target\"] != 0)\nexpected = [\"grey\" if t == 0 else \"red\" if d > tl else \"amber\" if d > tl * 0.6 else \"green\"\n            for t, d, tl in zip(lines[\"Target\"], dev, tol)]\nfor line, e, x in zip(lines[\"Line\"], expected, excel):\n    print(f\"{line}: pandas {e:\u003C6} Excel {x:\u003C6} {'ok' if e == x else 'MISMATCH'}\")\n",[37,1590,1591,1598,1609,1613,1626,1640,1666,1703,1778,1805,1821,1829,1887,1936,1940,1961,2007,2071,2092,2110],{"__ignoreMap":35},[40,1592,1593,1595],{"class":42,"line":43},[40,1594,84],{"class":83},[40,1596,1597],{"class":87}," subprocess\n",[40,1599,1600,1602,1604,1606],{"class":42,"line":97},[40,1601,100],{"class":83},[40,1603,103],{"class":87},[40,1605,84],{"class":83},[40,1607,1608],{"class":87}," load_workbook\n",[40,1610,1611],{"class":42,"line":111},[40,1612,128],{"emptyLinePlaceholder":127},[40,1614,1615,1617,1619,1622,1624],{"class":42,"line":124},[40,1616,340],{"class":87},[40,1618,137],{"class":83},[40,1620,1621],{"class":87}," load_workbook(",[40,1623,1480],{"class":50},[40,1625,442],{"class":87},[40,1627,1628,1630,1632,1635,1638],{"class":42,"line":131},[40,1629,351],{"class":87},[40,1631,137],{"class":83},[40,1633,1634],{"class":87}," wb[",[40,1636,1637],{"class":50},"\"Output\"",[40,1639,916],{"class":87},[40,1641,1642,1644,1647,1649,1652,1654,1656,1659,1661,1664],{"class":42,"line":191},[40,1643,478],{"class":83},[40,1645,1646],{"class":87}," r ",[40,1648,484],{"class":83},[40,1650,1651],{"class":199}," range",[40,1653,648],{"class":87},[40,1655,509],{"class":199},[40,1657,1658],{"class":87},", ws.max_row ",[40,1660,463],{"class":83},[40,1662,1663],{"class":199}," 1",[40,1665,512],{"class":87},[40,1667,1668,1671,1673,1676,1679,1682,1684,1687,1690,1692,1694,1696,1698,1700],{"class":42,"line":240},[40,1669,1670],{"class":87},"    tol, dev ",[40,1672,137],{"class":83},[40,1674,1675],{"class":199}," TOL",[40,1677,1678],{"class":87},".replace(",[40,1680,1681],{"class":50},"\"4\"",[40,1683,152],{"class":87},[40,1685,1686],{"class":199},"str",[40,1688,1689],{"class":87},"(r)), ",[40,1691,740],{"class":199},[40,1693,1678],{"class":87},[40,1695,1681],{"class":50},[40,1697,152],{"class":87},[40,1699,1686],{"class":199},[40,1701,1702],{"class":87},"(r))\n",[40,1704,1705,1707,1709,1712,1714,1716,1718,1720,1722,1724,1727,1729,1732,1734,1736,1738,1741,1743,1745,1747,1750,1752,1754,1756,1759,1761,1764,1766,1768,1770,1773,1775],{"class":42,"line":288},[40,1706,524],{"class":87},[40,1708,527],{"class":83},[40,1710,1711],{"class":50},"\"G",[40,1713,534],{"class":533},[40,1715,537],{"class":87},[40,1717,540],{"class":533},[40,1719,543],{"class":50},[40,1721,385],{"class":87},[40,1723,137],{"class":83},[40,1725,1726],{"class":87}," (",[40,1728,527],{"class":83},[40,1730,1731],{"class":50},"'=IF(AND($B",[40,1733,534],{"class":533},[40,1735,537],{"class":87},[40,1737,540],{"class":533},[40,1739,1740],{"class":50},"=0,$C",[40,1742,534],{"class":533},[40,1744,537],{"class":87},[40,1746,540],{"class":533},[40,1748,1749],{"class":50},"\u003C>0),\"grey\",IF($B",[40,1751,534],{"class":533},[40,1753,537],{"class":87},[40,1755,540],{"class":533},[40,1757,1758],{"class":50},"=0,\"\",IF(",[40,1760,534],{"class":533},[40,1762,1763],{"class":87},"dev",[40,1765,540],{"class":533},[40,1767,806],{"class":50},[40,1769,534],{"class":533},[40,1771,1772],{"class":87},"tol",[40,1774,540],{"class":533},[40,1776,1777],{"class":50},",\"red\",'\n",[40,1779,1780,1783,1786,1788,1790,1792,1794,1796,1798,1800,1803],{"class":42,"line":332},[40,1781,1782],{"class":83},"                   f",[40,1784,1785],{"class":50},"'IF(",[40,1787,534],{"class":533},[40,1789,1763],{"class":87},[40,1791,540],{"class":533},[40,1793,806],{"class":50},[40,1795,534],{"class":533},[40,1797,1772],{"class":87},[40,1799,540],{"class":533},[40,1801,1802],{"class":50},"*0.6,\"amber\",\"green\"))))'",[40,1804,442],{"class":87},[40,1806,1807,1810,1813,1816,1818],{"class":42,"line":337},[40,1808,1809],{"class":87},"ws.column_dimensions[",[40,1811,1812],{"class":50},"\"G\"",[40,1814,1815],{"class":87},"].hidden ",[40,1817,137],{"class":83},[40,1819,1820],{"class":199}," True\n",[40,1822,1823,1825,1827],{"class":42,"line":348},[40,1824,1477],{"class":87},[40,1826,1480],{"class":50},[40,1828,442],{"class":87},[40,1830,1831,1834,1837,1839,1842,1844,1847,1849,1852,1854,1857,1859,1862,1864,1866,1869,1872,1874,1876,1878,1881,1883,1885],{"class":42,"line":359},[40,1832,1833],{"class":87},"subprocess.run([",[40,1835,1836],{"class":50},"\"soffice\"",[40,1838,152],{"class":87},[40,1840,1841],{"class":50},"\"--headless\"",[40,1843,152],{"class":87},[40,1845,1846],{"class":50},"\"--convert-to\"",[40,1848,152],{"class":87},[40,1850,1851],{"class":50},"\"xlsx\"",[40,1853,152],{"class":87},[40,1855,1856],{"class":50},"\"--outdir\"",[40,1858,152],{"class":87},[40,1860,1861],{"class":50},"\"calc\"",[40,1863,152],{"class":87},[40,1865,1480],{"class":50},[40,1867,1868],{"class":87},"], ",[40,1870,1871],{"class":433},"check",[40,1873,137],{"class":83},[40,1875,1009],{"class":199},[40,1877,152],{"class":87},[40,1879,1880],{"class":433},"capture_output",[40,1882,137],{"class":83},[40,1884,1009],{"class":199},[40,1886,442],{"class":87},[40,1888,1889,1892,1894,1897,1899,1902,1904,1906,1909,1911,1914,1916,1918,1921,1923,1926,1928,1930,1933],{"class":42,"line":370},[40,1890,1891],{"class":87},"excel ",[40,1893,137],{"class":83},[40,1895,1896],{"class":87}," [c.value ",[40,1898,478],{"class":83},[40,1900,1901],{"class":87}," c ",[40,1903,484],{"class":83},[40,1905,1621],{"class":87},[40,1907,1908],{"class":50},"\"calc\u002Foutput_tolerance.xlsx\"",[40,1910,152],{"class":87},[40,1912,1913],{"class":433},"data_only",[40,1915,137],{"class":83},[40,1917,1009],{"class":199},[40,1919,1920],{"class":87},")[",[40,1922,1637],{"class":50},[40,1924,1925],{"class":87},"][",[40,1927,1812],{"class":50},[40,1929,1925],{"class":87},[40,1931,1932],{"class":199},"3",[40,1934,1935],{"class":87},":]]\n",[40,1937,1938],{"class":42,"line":398},[40,1939,128],{"emptyLinePlaceholder":127},[40,1941,1942,1945,1947,1950,1953,1956,1959],{"class":42,"line":413},[40,1943,1944],{"class":87},"tol ",[40,1946,137],{"class":83},[40,1948,1949],{"class":87}," lines[",[40,1951,1952],{"class":50},"\"Line tolerance\"",[40,1954,1955],{"class":87},"].fillna(",[40,1957,1958],{"class":199},"0.05",[40,1960,442],{"class":87},[40,1962,1963,1966,1968,1971,1973,1975,1978,1980,1982,1985,1988,1990,1992,1995,1997,1999,2002,2005],{"class":42,"line":445},[40,1964,1965],{"class":87},"dev ",[40,1967,137],{"class":83},[40,1969,1970],{"class":87}," (lines[",[40,1972,1375],{"class":50},[40,1974,385],{"class":87},[40,1976,1977],{"class":83},"-",[40,1979,1949],{"class":87},[40,1981,1370],{"class":50},[40,1983,1984],{"class":87},"]).abs() ",[40,1986,1987],{"class":83},"\u002F",[40,1989,1949],{"class":87},[40,1991,1370],{"class":50},[40,1993,1994],{"class":87},"].where(lines[",[40,1996,1370],{"class":50},[40,1998,385],{"class":87},[40,2000,2001],{"class":83},"!=",[40,2003,2004],{"class":199}," 0",[40,2006,442],{"class":87},[40,2008,2009,2012,2014,2016,2019,2022,2025,2028,2030,2033,2036,2038,2041,2043,2046,2049,2052,2054,2056,2058,2060,2063,2066,2068],{"class":42,"line":451},[40,2010,2011],{"class":87},"expected ",[40,2013,137],{"class":83},[40,2015,466],{"class":87},[40,2017,2018],{"class":50},"\"grey\"",[40,2020,2021],{"class":83}," if",[40,2023,2024],{"class":87}," t ",[40,2026,2027],{"class":83},"==",[40,2029,2004],{"class":199},[40,2031,2032],{"class":83}," else",[40,2034,2035],{"class":50}," \"red\"",[40,2037,2021],{"class":83},[40,2039,2040],{"class":87}," d ",[40,2042,806],{"class":83},[40,2044,2045],{"class":87}," tl ",[40,2047,2048],{"class":83},"else",[40,2050,2051],{"class":50}," \"amber\"",[40,2053,2021],{"class":83},[40,2055,2040],{"class":87},[40,2057,806],{"class":83},[40,2059,2045],{"class":87},[40,2061,2062],{"class":83},"*",[40,2064,2065],{"class":199}," 0.6",[40,2067,2032],{"class":83},[40,2069,2070],{"class":50}," \"green\"\n",[40,2072,2073,2076,2079,2081,2084,2087,2089],{"class":42,"line":475},[40,2074,2075],{"class":83},"            for",[40,2077,2078],{"class":87}," t, d, tl ",[40,2080,484],{"class":83},[40,2082,2083],{"class":199}," zip",[40,2085,2086],{"class":87},"(lines[",[40,2088,1370],{"class":50},[40,2090,2091],{"class":87},"], dev, tol)]\n",[40,2093,2094,2096,2099,2101,2103,2105,2107],{"class":42,"line":515},[40,2095,478],{"class":83},[40,2097,2098],{"class":87}," line, e, x ",[40,2100,484],{"class":83},[40,2102,2083],{"class":199},[40,2104,2086],{"class":87},[40,2106,143],{"class":50},[40,2108,2109],{"class":87},"], expected, excel):\n",[40,2111,2112,2115,2117,2119,2121,2123,2125,2127,2130,2132,2135,2138,2140,2143,2145,2148,2150,2152,2154,2157,2159,2162,2164,2167,2169,2172,2174,2176],{"class":42,"line":521},[40,2113,2114],{"class":199},"    print",[40,2116,648],{"class":87},[40,2118,527],{"class":83},[40,2120,543],{"class":50},[40,2122,534],{"class":533},[40,2124,42],{"class":87},[40,2126,540],{"class":533},[40,2128,2129],{"class":50},": pandas ",[40,2131,534],{"class":533},[40,2133,2134],{"class":87},"e",[40,2136,2137],{"class":83},":\u003C6",[40,2139,540],{"class":533},[40,2141,2142],{"class":50}," Excel ",[40,2144,534],{"class":533},[40,2146,2147],{"class":87},"x",[40,2149,2137],{"class":83},[40,2151,540],{"class":533},[40,2153,1045],{"class":533},[40,2155,2156],{"class":50},"'ok'",[40,2158,2021],{"class":83},[40,2160,2161],{"class":87}," e ",[40,2163,2027],{"class":83},[40,2165,2166],{"class":87}," x ",[40,2168,2048],{"class":83},[40,2170,2171],{"class":50}," 'MISMATCH'",[40,2173,540],{"class":533},[40,2175,543],{"class":50},[40,2177,442],{"class":87},[10,2179,2180,2181,2184],{},"Every line gets the same colour from the workbook formula as from pandas: L1 at −2% and L8 at −0.5% are green; L6 at −4.9% is amber, close to its 5% limit; L2 at +6.3% and L3 at −12% are red; L4, only 1.5% over but held to a 2% tolerance, is amber; L7, 9.7% over but allowed 15%, is amber too; and L5, producing on a zero target, is grey. The helper column uses the same expressions as the rules, so if it is right, the colours are right — and it stays in the file, hidden, as documentation of what the colours mean. Change the tolerance in B1 to 10% and the colours update without touching the rules: L2, at 6.3%, would then be amber, L6 would turn green, and only L3 would stay red — a quick way to show a manager what a looser standard would mean. Keeping the classification in one place also helps when the same logic is needed elsewhere — a summary count of red lines on a dashboard is simply ",[37,2182,2183],{},"COUNTIF"," over the hidden helper column.",[1081,2186,1090,2192,1090,2195,1090,2198,1090,2203,1090,2208,1090,2212,1090,2218,1090,2222,1090,2226,1090,2230,1090,2234,1090,2238,1090,2242,1090,2246,1090,2250,1090,2254,1090,2259,1090,2262,1090,2266,1090,2268,1090,2271,1090,2273,1090,2276,1090,2278,1090,2280,1090,2282,1090,2285,1090,2288,1090,2292,1090,2294,1090,2297,1090,2299,1090,2301,1090,2303,1090,2306,1090,2308,1090,2311,1090,2313,1090,2316,1090,2319,1090,2322,1090,2324,1090,2327,1090,2329,1090,2331,1090,2335,1090,2338,1090,2340,1090,2342,1090,2344,1090,2347,1090,2350,1090,2353,1090,2355,1090,2358,1090,2360,1090,2362,1090,2364,1090,2366,1090,2368,1090,2370,1090,2372,1090,2375,1090,2378,1090,2382,1090,2384,1090,2387,1090,2389,1090,2391,1090,2393,1090,2396,1090,2398,1090,2401,1090,2403,1090,2406,1090,2409,1090,2413,1090,2415,1090,2418,1090,2420,1090,2422,1090,2424,1090,2427,1090,2429,1090,2431,1090,2433,1090,2436,1090,2440,1090,2442,1090,2445,1090,2447,1090,2449,1090,2451,1090,2453,1090,2455,1090,2457,1090,2459],{"viewBox":2187,"role":1084,"ariaLabelledBy":2188,"xmlns":1088,"style":2191},"167 0 425 252",[2189,2190],"tol-result-t","tol-result-d","width:100%;max-width:425px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[1092,2193,2194],{"id":2189},"The output report after recalculation",[1096,2196,2197],{"id":2190},"L1 and L8 are green, close to target. L6 at minus 4.9 percent and L4 and L7, near their own tolerances, are amber. L2 at plus 6.3 percent and L3 at minus 12 percent are red. L5, producing against a zero target, is grey.",[1100,2199],{"x":2200,"y":220,"width":2201,"height":2202,"fill":1104},"167","425","252",[1100,2204],{"x":2205,"y":1108,"width":2206,"height":2207,"rx":220,"fill":1168,"stroke":1155,"style":1532},"183.9","32.0","24.0",[1100,2209],{"x":2210,"y":1108,"width":2211,"height":2207,"rx":220,"fill":1168,"stroke":1155,"style":1532},"215.9","49.9",[1127,2213,2217],{"x":2214,"y":2215,"style":2216},"240.8","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[1100,2219],{"x":2220,"y":1108,"width":2221,"height":2207,"rx":220,"fill":1168,"stroke":1155,"style":1532},"265.8","64.9",[1127,2223,2225],{"x":2224,"y":2215,"style":2216},"298.2","B",[1100,2227],{"x":2228,"y":1108,"width":2229,"height":2207,"rx":220,"fill":1168,"stroke":1155,"style":1532},"330.7","64.0",[1127,2231,2233],{"x":2232,"y":2215,"style":2216},"362.7","C",[1100,2235],{"x":2236,"y":1108,"width":2237,"height":2207,"rx":220,"fill":1168,"stroke":1155,"style":1532},"394.7","86.3",[1127,2239,2241],{"x":2240,"y":2215,"style":2216},"437.8","D",[1100,2243],{"x":2244,"y":1108,"width":2245,"height":2207,"rx":220,"fill":1168,"stroke":1155,"style":1532},"481.0","95.2",[1127,2247,2249],{"x":2248,"y":2215,"style":2216},"528.6","E",[1100,2251],{"x":2205,"y":2252,"width":2206,"height":2253,"rx":220,"fill":1168,"stroke":1155,"style":1532},"40.0","28.0",[1127,2255,2258],{"x":2256,"y":2257,"style":2216},"199.9","59.0","1",[1100,2260],{"x":2210,"y":2252,"width":2211,"height":2253,"rx":220,"fill":1112,"stroke":1113,"style":2261},"stroke-width:1.5px",[1127,2263,2265],{"x":2214,"y":2257,"style":2264},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Line",[1100,2267],{"x":2220,"y":2252,"width":2221,"height":2253,"rx":220,"fill":1112,"stroke":1113,"style":2261},[1127,2269,2270],{"x":2224,"y":2257,"style":2264},"Target",[1100,2272],{"x":2228,"y":2252,"width":2229,"height":2253,"rx":220,"fill":1112,"stroke":1113,"style":2261},[1127,2274,2275],{"x":2232,"y":2257,"style":2264},"Actual",[1100,2277],{"x":2236,"y":2252,"width":2237,"height":2253,"rx":220,"fill":1112,"stroke":1113,"style":2261},[1127,2279,671],{"x":2240,"y":2257,"style":2264},[1100,2281],{"x":2244,"y":2252,"width":2245,"height":2253,"rx":220,"fill":1112,"stroke":1113,"style":2261},[1127,2283,2284],{"x":2248,"y":2257,"style":2264},"Variance %",[1100,2286],{"x":2205,"y":2287,"width":2206,"height":2253,"rx":220,"fill":1168,"stroke":1155,"style":1532},"68.0",[1127,2289,2291],{"x":2256,"y":2290,"style":2216},"87.0","2",[1100,2293],{"x":2210,"y":2287,"width":2211,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2295,2296],{"x":2214,"y":2290,"style":1536},"L1",[1100,2298],{"x":2220,"y":2287,"width":2221,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2300,200],{"x":2224,"y":2290,"style":1536},[1100,2302],{"x":2228,"y":2287,"width":2229,"height":2253,"rx":220,"fill":1261,"stroke":1262,"style":2261},[1127,2304,248],{"x":2232,"y":2290,"style":2305},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[1100,2307],{"x":2236,"y":2287,"width":2237,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2309,2310],{"x":2240,"y":2290,"style":1536},"5%",[1100,2312],{"x":2244,"y":2287,"width":2245,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2314,2315],{"x":2248,"y":2290,"style":1536},"-2.0%",[1100,2317],{"x":2205,"y":2318,"width":2206,"height":2253,"rx":220,"fill":1168,"stroke":1155,"style":1532},"96.0",[1127,2320,1932],{"x":2256,"y":2321,"style":2216},"115.0",[1100,2323],{"x":2210,"y":2318,"width":2211,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2325,2326],{"x":2214,"y":2321,"style":1536},"L2",[1100,2328],{"x":2220,"y":2318,"width":2221,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2330,205],{"x":2224,"y":2321,"style":1536},[1100,2332],{"x":2228,"y":2318,"width":2229,"height":2253,"rx":220,"fill":2333,"stroke":2334,"style":2261},"#fce9e9","var(--danger,#dc2626)",[1127,2336,253],{"x":2232,"y":2321,"style":2337},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[1100,2339],{"x":2236,"y":2318,"width":2237,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2341,2310],{"x":2240,"y":2321,"style":1536},[1100,2343],{"x":2244,"y":2318,"width":2245,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2345,2346],{"x":2248,"y":2321,"style":1536},"+6.3%",[1100,2348],{"x":2205,"y":2349,"width":2206,"height":2253,"rx":220,"fill":1168,"stroke":1155,"style":1532},"124.0",[1127,2351,509],{"x":2256,"y":2352,"style":2216},"143.0",[1100,2354],{"x":2210,"y":2349,"width":2211,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2356,2357],{"x":2214,"y":2352,"style":1536},"L3",[1100,2359],{"x":2220,"y":2349,"width":2221,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2361,210],{"x":2224,"y":2352,"style":1536},[1100,2363],{"x":2228,"y":2349,"width":2229,"height":2253,"rx":220,"fill":2333,"stroke":2334,"style":2261},[1127,2365,258],{"x":2232,"y":2352,"style":2337},[1100,2367],{"x":2236,"y":2349,"width":2237,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2369,2310],{"x":2240,"y":2352,"style":1536},[1100,2371],{"x":2244,"y":2349,"width":2245,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2373,2374],{"x":2248,"y":2352,"style":1536},"-12.0%",[1100,2376],{"x":2205,"y":2377,"width":2206,"height":2253,"rx":220,"fill":1168,"stroke":1155,"style":1532},"152.0",[1127,2379,2381],{"x":2256,"y":2380,"style":2216},"171.0","5",[1100,2383],{"x":2210,"y":2377,"width":2211,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2385,2386],{"x":2214,"y":2380,"style":1536},"L4",[1100,2388],{"x":2220,"y":2377,"width":2221,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2390,215],{"x":2224,"y":2380,"style":1536},[1100,2392],{"x":2228,"y":2377,"width":2229,"height":2253,"rx":220,"fill":1230,"stroke":1231,"style":2261},[1127,2394,263],{"x":2232,"y":2380,"style":2395},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[1100,2397],{"x":2236,"y":2377,"width":2237,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2399,2400],{"x":2240,"y":2380,"style":1536},"2%",[1100,2402],{"x":2244,"y":2377,"width":2245,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2404,2405],{"x":2248,"y":2380,"style":1536},"+1.5%",[1100,2407],{"x":2205,"y":2408,"width":2206,"height":2253,"rx":220,"fill":1168,"stroke":1155,"style":1532},"180.0",[1127,2410,2412],{"x":2256,"y":2411,"style":2216},"199.0","6",[1100,2414],{"x":2210,"y":2408,"width":2211,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2416,2417],{"x":2214,"y":2411,"style":1536},"L5",[1100,2419],{"x":2220,"y":2408,"width":2221,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2421,220],{"x":2224,"y":2411,"style":1536},[1100,2423],{"x":2228,"y":2408,"width":2229,"height":2253,"rx":220,"fill":1168,"stroke":1155,"style":2261},[1127,2425,268],{"x":2232,"y":2411,"style":2426},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[1100,2428],{"x":2236,"y":2408,"width":2237,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2430,2310],{"x":2240,"y":2411,"style":1536},[1100,2432],{"x":2244,"y":2408,"width":2245,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1100,2434],{"x":2205,"y":2435,"width":2206,"height":2253,"rx":220,"fill":1168,"stroke":1155,"style":1532},"208.0",[1127,2437,2439],{"x":2256,"y":2438,"style":2216},"227.0","7",[1100,2441],{"x":2210,"y":2435,"width":2211,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2443,2444],{"x":2214,"y":2438,"style":1536},"L6",[1100,2446],{"x":2220,"y":2435,"width":2221,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2448,225],{"x":2224,"y":2438,"style":1536},[1100,2450],{"x":2228,"y":2435,"width":2229,"height":2253,"rx":220,"fill":1230,"stroke":1231,"style":2261},[1127,2452,273],{"x":2232,"y":2438,"style":2395},[1100,2454],{"x":2236,"y":2435,"width":2237,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2456,2310],{"x":2240,"y":2438,"style":1536},[1100,2458],{"x":2244,"y":2435,"width":2245,"height":2253,"rx":220,"fill":1104,"stroke":1155,"style":1532},[1127,2460,2461],{"x":2248,"y":2438,"style":1536},"-4.9%",[25,2463,2465],{"id":2464},"common-pitfalls","Common pitfalls",[2467,2468,2469,2477,2483,2491,2497],"ul",{},[2470,2471,2472,2476],"li",{},[2473,2474,2475],"strong",{},"Fixed thresholds for per-row targets."," Use formula rules that read each row's target.",[2470,2478,2479,2482],{},[2473,2480,2481],{},"Dividing by a zero target."," Give zero targets their own rule before the percentage rules.",[2470,2484,2485,2488,2489,23],{},[2473,2486,2487],{},"Rules in the wrong order."," Order from specific to general and use ",[37,2490,1004],{},[2470,2492,2493,2496],{},[2473,2494,2495],{},"Tolerance hard-coded in rules."," Put it in a named cell readers can change.",[2470,2498,2499,2502],{},[2473,2500,2501],{},"Untested rules."," Mirror them in a helper formula and compare after recalculation.",[25,2504,2506],{"id":2505},"conclusion","Conclusion",[10,2508,2509,2510,2513,2514,2516],{},"To highlight values by their distance from a per-row target, write formula rules on the actual column that compare ",[37,2511,2512],{},"ABS(actual − target) \u002F target"," with the row's tolerance — its own column if set, otherwise a named default cell — and order them zero target, outside, near limit, within, each with ",[37,2515,1004],{},". Use an absolute tolerance where percentages mislead, and verify the logic by mirroring it in a helper formula and comparing the recalculated results with pandas.",[25,2518,2520],{"id":2519},"frequently-asked-questions","Frequently asked questions",[10,2522,2523,2526],{},[2473,2524,2525],{},"How do I highlight values that miss their target by more than 5%?","\nAdd a FormulaRule on the actual column with a formula such as ABS($C4-$B4)\u002F$B4>0.05, written for the first data row; Excel applies it to each row with that row's target.",[10,2528,2529,2532],{},[2473,2530,2531],{},"How can readers change the tolerance used by conditional formatting?","\nPut the tolerance in a cell, give it a name, and refer to the name in the rule formulas; changing the cell updates every highlight.",[10,2534,2535,2538],{},[2473,2536,2537],{},"How do I handle a target of zero in a percentage rule?","\nAdd a rule for zero targets first, with stopIfTrue, and include $B4\u003C>0 in the percentage rules so they never divide by zero.",[10,2540,2541,2544],{},[2473,2542,2543],{},"Can each row have its own tolerance?","\nYes. Store it in a column and use IF(ISNUMBER($D4),$D4,Tolerance) in the rules so rows without one fall back to the default.",[25,2546,2548],{"id":2547},"related","Related",[2467,2550,2551,2557,2564,2571],{},[2470,2552,2553,2554,2556],{},"Up one level: ",[19,2555,22],{"href":21}," — rules from Python.",[2470,2558,2559,2563],{},[19,2560,2562],{"href":2561},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-an-entire-row-based-on-a-cell-value-with-openpyxl\u002F","Highlight an Entire Row Based on a Cell Value with openpyxl"," — extending a highlight across the row.",[2470,2565,2566,2570],{},[19,2567,2569],{"href":2568},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-monthly-kpi-scorecard-with-targets\u002F","Build a Monthly KPI Scorecard with Targets"," — targets in a scorecard.",[2470,2572,2573,2577],{},[19,2574,2576],{"href":2575},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-with-formula-errors-using-conditional-formatting\u002F","Highlight Cells with Formula Errors Using Conditional Formatting"," — another formula-based rule.",[2579,2580,2581],"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 .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":35,"searchDepth":97,"depth":97,"links":2583},[2584,2585,2586,2587,2588,2589,2590,2591,2592],{"id":27,"depth":97,"text":28},{"id":67,"depth":97,"text":68},{"id":683,"depth":97,"text":684},{"id":1279,"depth":97,"text":1280},{"id":1581,"depth":97,"text":1582},{"id":2464,"depth":97,"text":2465},{"id":2505,"depth":97,"text":2506},{"id":2519,"depth":97,"text":2520},{"id":2547,"depth":97,"text":2548},"2026-10-02","Colour each actual figure by how far it is from its own target — green within tolerance, amber close to the limit, red outside it — with openpyxl conditional formatting: percentage and absolute tolerances, a tolerance setting readers can change, per-row tolerances, targets of zero, and a recalculated check that the rules flag exactly the rows pandas expects.","md",[2597,2599,2601,2603],{"q":2525,"a":2598},"Add a FormulaRule on the actual column with a formula such as ABS($C4-$B4)\u002F$B4>0.05, written for the first data row; Excel applies it to each row with that row's target.",{"q":2531,"a":2600},"Put the tolerance in a cell, give it a name, and refer to the name in the rule formulas; changing the cell updates every highlight.",{"q":2537,"a":2602},"Add a rule for zero targets first, with stopIfTrue, and include $B4\u003C>0 in the percentage rules so they never divide by zero.",{"q":2543,"a":2604},"Yes. Store it in a column and use IF(ISNUMBER($D4),$D4,Tolerance) in the rules so rows without one fall back to the default.","highlight values outside tolerance of target openpyxl",null,{"breadcrumb":2608},[2609,2611,2614],{"name":2610,"item":1987},"Home",{"name":2612,"item":2613},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":2615,"item":21},"Applying Conditional Formatting With openpyxl","\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-values-outside-a-tolerance-of-target-with-openpyxl",{"title":5,"description":2618},"Tolerance-band conditional formatting in openpyxl: actual versus target with percentage or absolute limits, a named tolerance cell, per-row tolerances, zero targets, amber warning bands, and verifying the rules against pandas.","highlight-values-outside-a-tolerance-of-target-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-values-outside-a-tolerance-of-target-with-openpyxl\u002Findex",[2622,2623],"conditional-formatting","openpyxl","how-to","-gPTY1IAH3tyNo8YlUPjdlbvrJaIM8wFasbxoYYOvPA",[2627,2631],{"title":2628,"path":2629,"stem":2630,"children":-1},"Highlight Top and Bottom Values in Excel with openpyxl","\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-top-and-bottom-values-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-top-and-bottom-values-with-openpyxl\u002Findex",{"title":2632,"path":2633,"stem":2634,"children":-1},"Highlight Weekends and Holidays in a Date Column","\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-weekends-and-holidays-in-a-date-column","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-weekends-and-holidays-in-a-date-column\u002Findex",[2636,2638],{"path":2637,"title":2615},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl",{"path":2639,"title":2612},"\u002Fadvanced-data-transformation-and-cleaning",[],[2642,2645,2648,2651,2654,2657],{"path":2643,"title":2644,"kind":2624},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter","Apply Conditional Formatting with xlsxwriter",{"path":2646,"title":2647,"kind":2624},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl","Add Data Bars and Colour Scales with openpyxl",{"path":2649,"title":2650,"kind":2624},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl","Add Icon Sets to Excel Cells with openpyxl",{"path":2652,"title":2653,"kind":2624},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fapply-a-colour-scale-per-row-or-per-column-with-openpyxl","Apply a Colour Scale per Row or per Column with openpyxl",{"path":2655,"title":2656,"kind":2624},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fapply-conditional-formatting-to-an-excel-table-that-grows","Apply Conditional Formatting to an Excel Table That Grows",{"path":2658,"title":1079,"kind":2624},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fcontrol-conditional-formatting-rule-order-and-stop-if-true",1790972078359]