Website: machine estimator v3 — the bar shows what the box's memory is used for (still unlisted, R-997)
gates / gates (push) Successful in 6m29s
gates / gates (push) Successful in 6m29s
The bar keeps its length (the fits-well box memory) and is split into base system (box - guest), Felhom's own services, each app's peak (largest first, the dashboard's palette minus amber and its first blue; beyond 8 apps one group) and spare room (guest - peak) — all from the one rule; a test proves the parts sum to the bar within 1 MB. A legend under it (not sticky) and one sentence explain the recommended size. Typical figure moved into the method fold-out. English: 'an 8 GB machine'.
This commit is contained in:
@@ -1,3 +1,12 @@
|
||||
## 2026-10-13 (evening) — estimator v3: segments and the English article under test
|
||||
|
||||
- `build_sizing_web.py`: `bar_app_colours` (8) in the data file, so the script holds no number of its own.
|
||||
- `test_build_sizing_web.py` (10 tests): the bar's segments (base system = box − guest, Felhom, each app's peak,
|
||||
spare = guest − peak) add up to the bar within 1 MB for the four cases and for nothing selected; base and spare are
|
||||
never negative; the household preset by hand (spare = 5,515 × 0.5, base = 4,096 + 612); every English
|
||||
recommendation gets „an 8" / „a 12" … Red-proofed: the „an 8" rule removed, the JS computing spare from the typical
|
||||
figure, and segments off by 50 MB each convicted.
|
||||
|
||||
## 2026-10-13 (afternoon) — estimator v2: `build_sizing_web.py` box sizes, tests for the inverse
|
||||
|
||||
- `build_sizing_web.py`: `boxes_gb` = 8, 12, 16, 24, 32, 48, 64; new `bar_marks_gb` and `bar_max_gb`.
|
||||
|
||||
@@ -36,6 +36,7 @@ GUEST_RULE = {"reserve_mib": 4096, "max_gap_mib": 2048, "min_mib": 4096, "ceilin
|
||||
BOXES_GB = [8, 12, 16, 24, 32, 48, 64] # the common sizes a recommendation names (v2: 12, 24, 48, 64 added)
|
||||
BAR_MARKS_GB = [8, 16, 24, 32, 48, 64] # the memory bar's marks; the last one reads "64+"
|
||||
BAR_MAX_GB = 64 # the bar's linear scale is 0..BAR_MAX_GB
|
||||
BAR_APP_COLOURS = 8 # v3: apps get their own colour up to this many; the rest join one segment
|
||||
BUILTIN = {"filebrowser"}
|
||||
|
||||
# One short line per medium / heavy-in-bursts app (the 9 of 2026-10-13). sizing.json's English notes, tidied.
|
||||
@@ -119,6 +120,7 @@ def build():
|
||||
boxes_gb=BOXES_GB,
|
||||
bar_marks_gb=BAR_MARKS_GB,
|
||||
bar_max_gb=BAR_MAX_GB,
|
||||
bar_app_colours=BAR_APP_COLOURS,
|
||||
categories=[dict(id=h[0], id_en=e[0], name_hu=h[1], name_en=e[1]) for h, e in zip(hu_cats, en_cats)],
|
||||
apps=apps,
|
||||
)
|
||||
|
||||
@@ -59,6 +59,16 @@ def estimate(d, slugs): # mirrors sizing.js estimate()
|
||||
return peak, out
|
||||
|
||||
|
||||
def segments(d, slugs): # mirrors estimate().segments in sizing.js (v3)
|
||||
by = {a["slug"]: a for a in d["apps"]}
|
||||
apps = sorted((by[s] for s in slugs if not by[s].get("builtin")), key=lambda a: -a["peak_mb"])
|
||||
peak = d["platform_mb"] + sum(a["peak_mb"] for a in apps)
|
||||
well = min_box_gb(d, peak * d["headroom"])
|
||||
box, guest = well * 1024, guest_mib(d, well)
|
||||
return well, [("base", box - guest), ("platform", d["platform_mb"])] + \
|
||||
[(a["slug"], a["peak_mb"]) for a in apps] + [("spare", guest - peak)]
|
||||
|
||||
|
||||
def first(d, verdicts, want): # mirrors recommend_gb / tight_box_gb in sizing.js
|
||||
return next((gb for gb, v in zip(d["boxes_gb"], verdicts) if v == want), None)
|
||||
|
||||
@@ -112,6 +122,32 @@ class SizingWeb(unittest.TestCase):
|
||||
if rec is not None:
|
||||
self.assertGreaterEqual(rec, gb - 1 / 1024)
|
||||
|
||||
def test_segments_add_up_to_the_bar(self):
|
||||
for name, sel in {"household": HOUSEHOLD, "household without immich": [x for x in HOUSEHOLD if x != "immich"],
|
||||
"immich alone": ["immich"], "all 62": self.all, "nothing": []}.items():
|
||||
well, segs = segments(self.d, sel)
|
||||
self.assertAlmostEqual(sum(mb for _, mb in segs), well * 1024, delta=1, msg=name)
|
||||
self.assertGreaterEqual(dict(segs)["base"], 0, name + ": base system negative")
|
||||
self.assertGreaterEqual(dict(segs)["spare"], 0, name + ": spare negative")
|
||||
# the household preset, by hand: peak 5,515 MiB; the bar holds 5,515 * 1.5 = 8,272.5 in the guest
|
||||
well, segs = segments(self.d, HOUSEHOLD)
|
||||
self.assertAlmostEqual(dict(segs)["spare"], 5515 * 0.5, delta=1)
|
||||
self.assertAlmostEqual(dict(segs)["base"], 4708, delta=1) # 4,096 for Proxmox + 612 MemTotal shortfall
|
||||
|
||||
def test_english_article(self):
|
||||
with io.open(os.path.join(ROOT, "website", "en", "sizing.html"), encoding="utf-8-sig") as f:
|
||||
t = json.loads(re.search(r'id="sizer-strings">(.*?)</script>', f.read(), re.S).group(1))
|
||||
def say(tpl, gb): # mirrors fill() in sizing.js
|
||||
art = t["an"] if str(gb) in t["an_before"] else t["a"]
|
||||
return tpl.replace("{a}", art).replace("{gb}", str(gb))
|
||||
got = {gb: say(t["recommend"], gb) for gb in self.d["boxes_gb"]}
|
||||
self.assertEqual(got[8], "Recommended: an 8 GB machine.")
|
||||
for gb in self.d["boxes_gb"]:
|
||||
if gb != 8:
|
||||
self.assertEqual(got[gb], "Recommended: a %d GB machine." % gb)
|
||||
for k in ("tight_on", "only_tight", "none_fits"):
|
||||
self.assertNotRegex(say(t[k], 8), r"\ba 8\b", k)
|
||||
|
||||
def test_sizing_md_table_agrees_with_the_rule(self):
|
||||
with io.open(SIZING_MD, encoding="utf-8") as f:
|
||||
s = f.read()
|
||||
@@ -146,7 +182,8 @@ class SizingWeb(unittest.TestCase):
|
||||
nums = set(re.findall(r"(?<![\w.])\d+(?:\.\d+)?", code))
|
||||
allowed = {"0", "1", "2", "100", "1024"} # 100 = percent, 1024 = MiB per GiB; no rule constant
|
||||
self.assertTrue(nums <= allowed, "sizing.js holds its own numbers: %s" % sorted(nums - allowed))
|
||||
for need in ("data.bar_max_gb", "data.bar_marks_gb", "function minBoxGb", "guestMib(data, mid) >= needMib"):
|
||||
for need in ("data.bar_max_gb", "data.bar_marks_gb", "function minBoxGb", "guestMib(data, mid) >= needMib",
|
||||
"data.bar_app_colours", "boxMb - guestMb", "guestMb - peak"):
|
||||
self.assertIn(need, js)
|
||||
|
||||
def test_data_has_no_peak_below_typical(self):
|
||||
|
||||
Reference in New Issue
Block a user