WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s

History-free snapshot of the private development repository at cbe45c5
(2026-09-09). Code, docs and public catalogue only; no plant or job
knowledge layers, no client or job names (tools/scrub.py clean). The
intro deck is left out of this snapshot because its slide images could
not be checked for job drawings.
This commit is contained in:
2026-09-09 08:32:15 -05:00
commit 8e6ee31596
406 changed files with 223435 additions and 0 deletions
@@ -0,0 +1,805 @@
# -*- coding: utf-8 -*-
# Owner, verbatim (2026-08-30): "我想 reinforce 一个 piece,然后 AI 帮我找最
# 合适的几个母版 ... 这几个母版拼吧拼吧能把这个 panel 给拼出来 ... 每一个细节
# 都要照顾到,但是尽量选少的母版,覆盖多的细节。"
# Owner, verbatim (2026-08-30), on the measurement unit: "实际测量全部用英寸,
# 带两位小数。"
# Owner, verbatim (2026-08-30), on exactness ranking, not classifying:
# "如果有完全一样的形状,那么这个会在推荐的前侧。如果只是相似,那么就排在后
# 面。"
# Owner, verbatim (2026-08-30), on the donor cap: "一个方案,只要找到足够覆盖
# 全部特征的母版,就够了。我期待是 1-4 个,最多不超过 4 个。"
"""The reinforcing-suggestion matcher: a set-cover solver over opaque features.
WHAT THIS ANSWERS
-----------------
Will, 2026-08-30: "I want to reinforce a piece, and have AI find me the
best-fitting master(s) ... these masters, pieced together, can reconstruct
this panel ... every detail has to be accounted for, but pick as few
masters as possible, covering the most detail."
That is not a similarity ranking (the 08-26 attempt built one of those, and it
answers a different question). It is SET COVER: every feature of the target
panel must be accounted for by some donor, and the number of donors must be as
small as possible. Donors may overlap; overlap is expected and is not a
conflict (ruling, 2026-08-30). Coverage is counted BY FEATURE, not by area --
the target's small end features appear identically in both candidate donors of
the worked example, so an area weighting would let two big openings drown them
out, while counting features keeps each one a thing that must be covered.
THE SEAM, AND WHY IT IS HERE ON PURPOSE
---------------------------------------
This module never reads a field off a feature record. Not one. Every
comparison of two features happens inside a single injected callable, `covers`,
whose contract is stated below. Everything else in this file -- coverage, set
selection, ranking, tie-breaking -- works on opaque records and on the numbers
that callable returns.
That boundary is not a scheduling convenience, and it should survive after the
index lands. Comparing two precast features is the one part of this problem
that needs engineering judgement about geometry: what makes a blockout "the
same" blockout, whether a notch at the same station but a different Z band is
the same feature, how a through opening compares to a blind one. That judgement
is exactly the thing this project has been burned by before -- the shim check
that measured the wrong axis, the clash check that reported nothing because a
cylinder's edge curves close on themselves. Isolating it in one small callable
means it can be argued about, measured, and replaced without touching a line of
the selection logic, and means the selection logic can be tested to death on
synthetic data with no model, no licence and no Revit.
So: do not add a fallback here that peeks at a field "just in case". If the
matcher needs something `covers` cannot give it, that is a finding to report,
not a thing to work around.
THE `covers` CONTRACT
---------------------
covers(donor_feature, target_feature) -> None or float
None this donor feature does not cover that target feature at all.
0.0 exact match.
> 0.0 covers it; the number is how far from exact, smaller being closer.
Requirements on any implementation:
1. TOTAL. It must accept any pair of records the caller passes and must not
raise. A pair it cannot judge is `None`, not an exception.
2. PURE AND DETERMINISTIC. Same two records, same answer, every time, with no
dependence on call order or on any global. The matcher's determinism rests
entirely on this.
3. DIRECTIONAL. `covers(a, b)` need not equal `covers(b, a)`. A donor feature
may stand in for a target feature without the reverse being true.
4. COMMENSURABLE, IN INCHES. The returned distances are compared and sorted
ACROSS DIFFERENT FEATURE PAIRS and across different feature kinds. A
distance of 0.4 on a notch must mean the same amount of "off" as 0.4 on a
blockout, or the ranking is comparing apples to oranges. ONE UNIT FOR ALL
KINDS, and that unit is INCHES (owner's ruling, 2026-08-30:
"all actual measurements use inches, to two decimal places"). The owner answered a UNIT question, not a
WEIGHTING one -- there is no per-kind weight here and none is invented; an
inch off on a blockout is an inch off on a notch.
THE STORED GEOMETRY IS STILL FEET and must stay that way: feet is Revit's
internal unit and `reinforcecore` caches feet. The production `covers`
(`reinforcebind.covers`) is the single place the crossing happens, via
`reinforcecore.inches()`. Nothing in THIS module converts anything; it
sorts numbers and does not know what they measure.
PRECISION IS NOT THE SAME AS DISPLAY. Two-decimal inches is 0.01", an
order of magnitude finer than any tolerance this tool uses (dimensions
3/8", interference 1/8"). The production `covers` therefore returns
distances QUANTIZED TO 1/8 in so the ranking cannot be steered by
differences the measurement cannot support. This module imposes nothing:
it will rank whatever grain it is handed. See `reinforcebind.GRAIN_IN`.
5. ABSOLUTE, NOT PROPORTIONAL. Positions compare as absolute distance from the
start of the panel, never as a fraction of panel length. Measured: the two
big openings of the worked example land at the identical feet-from-start
rectangle on panels of two different lengths, and a proportional comparison
would have missed the match entirely. Nothing in this module normalises by
length, and nothing in `covers` should either.
6. TWO-DIMENSIONAL. The same measurement showed two donor features sharing an
x-range while sitting in different Z bands. A single-axis comparison calls
those the same feature. They are not.
7. CHEAP. It is called exactly once per (donor, donor feature, target
feature) triple -- the matcher builds the whole comparison table up front
and never calls it again -- but that is still on the order of a few
thousand calls per run.
Note what the contract does NOT contain: a similarity threshold. There is none,
anywhere in this module, by ruling -- "if there is a completely identical
shape, that ranks at the front of the recommendation; if it is only similar,
it goes further back." Exactness RANKS, it does not CLASSIFY. The
matcher never asks "are these two the same?", only "which of these is closer?",
which is arithmetic rather than an unfalsifiable judgement call. The one
classification that does exist -- covers-at-all vs. does-not -- lives inside
`covers`, where the geometry knowledge is, and it is the only place it may live.
WHAT THE CALLER PASSES
----------------------
suggest(target_features, donors, covers, ...)
`target_features` is a sequence of opaque feature records. `donors` is a
sequence of `(key, feature_list)` pairs -- the key is any hashable value that
identifies the donor to the caller (an element id, a mark, a tuple); this
module treats it as a label and nothing else. Feature records are whatever the
index produces; this module only ever hands them to `covers`.
SAME ELEVATION, RULED 2026-09-02 -- superseding the note this module used to
carry about donor order being the lever for this. WP425 ranked five donors in
a dead tie on every criterion below, and WP234 -- which shares WP425's own
building elevation -- printed last, by mark order, because nothing here knew
elevation existed: owner, "WP234明明在同一个elevation,为什么排名没有提前?"
Approved as briefed the same day ("同意,就这么做"): elevation is an AFFINITY,
the LAST tie-break -- after fewest donors, most exact, most reviewed and
total distance, before mark order -- never a filter, exactly the shape
Will's standing correction on donor order already described (a cross-design
or cross-fit exclusion is not what this is; a better-shaped cross-elevation
donor still wins). It is now an explicit ranking term, `same_elevation`,
rather than a reordering of the input list: see `Proposal.same_elevation`
and the ranking notes below. Donor input order is still the true last
resort -- it decides a tie elevation cannot, same as it always has.
WHY A TERM AND NOT THE ORDERING TRICK THIS MODULE USED TO SUGGEST. Sorting
same-elevation donors to the front of the input list would have worked for
the reported case (a single donor covers everything, so the tie-break really
is just "which donor is listed first"), but it hides the reason inside sort
position -- a report that wants to say WHY WP234 won would have to
recompute the same ordering itself. An explicit term, exactly like `masters`
below, is a fact the caller can read straight off the winning `Proposal` and
print.
AT MOST FOUR DONORS IN A PROPOSAL
---------------------------------
Owner's ruling, 2026-08-30: "for one proposal, it is enough to find masters
that cover all the features between them. I'd expect 1-4, never more than
4." `DEFAULT_MAX_DONORS = 4`, and it is a HARD
cap on the donors WITHIN one proposal. Do not confuse it with `max_proposals`,
which is a different axis entirely: `max_proposals` is how many alternative
answers get printed, `max_donors` is how big any one answer may be.
WHAT HAPPENS WHEN FOUR CANNOT COVER EVERYTHING -- INFERRED, NOT STATED. The
owner ruled the cap; he did not rule the overflow. The reading implemented
here is that the cap is hard: the search returns the best partial cover of
four donors and NAMES what it could not reach, rather than relaxing to a
fifth. That is already this module's established behaviour for the other
reason a feature goes uncovered (nobody has it at all), it is the answer the
first live run gave, and a suggestion that quietly grew to six donors would
not be the thing that was asked for. The two causes are reported separately
and never merged -- see `CAPPED` below.
Everything here is IronPython 2.7 compatible: no f-strings, no pathlib, no
modern typing, no Python 3 `functools`.
"""
# --------------------------------------------------------------------------
# Outcome vocabulary. "Could not judge" and "judged and found nothing" are
# different answers and must read differently -- the standing convention on
# this project, learned from a check that reported a clean result when it had
# in fact measured nothing.
# --------------------------------------------------------------------------
NO_DONORS = 'no_donors' # nothing eligible was offered; nothing was judged
NO_COVERAGE = 'no_coverage' # donors were judged; not one covers any feature
PARTIAL = 'partial' # some feature no donor covers, in any combination
CAPPED = 'capped' # every feature IS coverable; four donors cannot reach them all
COMPLETE = 'complete' # every target feature is covered
# PARTIAL and CAPPED are different findings and must never be collapsed.
# PARTIAL means nobody in the pool has that feature -- the engineer details it
# from scratch. CAPPED means somebody does have it, but not within the four
# donors a proposal is allowed; the engineer can go and find that donor
# himself. When both are true the status is PARTIAL (the harder fact) and
# `Suggestion.capped_out` still names the second set.
# `suggest` returns None -- not an empty result -- when the input cannot be
# judged at all (no target features). An empty `proposals` with a status is
# always the result of an actual judgement.
DEFAULT_MAX_PROPOSALS = 5
# How many donors ONE proposal may contain. The owner's number. This is not
# `max_proposals` -- see the module docstring; conflating the two axes gives
# you either one giant answer or four tiny ones.
DEFAULT_MAX_DONORS = 4
class Proposal(object):
"""One set of donors, and what each target feature got from it."""
def __init__(self, donor_indexes, donor_keys, assignment, uncovered,
master_keys=(), same_elevation_keys=()):
self.donor_indexes = tuple(donor_indexes)
self.donors = tuple(donor_keys)
# Which of THIS proposal's donors are reviewed templates, in the same
# order as `donors`. A label and a tie-break, never a filter.
self.masters = tuple(k for k in self.donors if k in set(master_keys))
self.master_count = len(self.masters)
# Which of THIS proposal's donors share the target's building
# elevation, same shape as `masters` and ranked directly below it --
# see the module docstring's 2026-09-02 note and the ranking
# comment above `_proposal_sort_key`. A label and a tie-break,
# never a filter: a donor whose elevation is unknown, or differs, is
# simply absent from `same_elevation_keys` and this stays empty.
self.same_elevation = tuple(
k for k in self.donors if k in set(same_elevation_keys))
self.elevation_count = len(self.same_elevation)
# assignment[i] is None (feature i uncovered by this proposal) or a
# 3-tuple (donor_index, donor_feature_index, distance).
self.assignment = tuple(assignment)
self.uncovered = tuple(uncovered)
dists = [a[2] for a in self.assignment if a is not None]
# Ascending: the ranking key. See `_proposal_sort_key`.
self.distances = tuple(sorted(dists))
self.exact_count = len([d for d in self.distances if d == 0.0])
self.covered_count = len(self.distances)
self.total_distance = sum(self.distances) if self.distances else 0.0
self.is_exact = (self.covered_count > 0 and
self.exact_count == self.covered_count)
def donor_for(self, feature_index):
"""(donor_key, donor_feature_index, distance) for one target feature.
None when this proposal does not cover that feature.
"""
got = self.assignment[feature_index]
if got is None:
return None
return (self.donors[self.donor_indexes.index(got[0])], got[1], got[2])
def is_master(self, donor_key):
return donor_key in self.masters
def __repr__(self):
return ('<Proposal {0} exact={1}/{2} master={3} elevation={4} '
'total={5:.4f}>').format(
list(self.donors), self.exact_count, self.covered_count,
self.master_count, self.elevation_count, self.total_distance)
class Suggestion(object):
"""The whole answer: ranked proposals plus what could not be covered."""
def __init__(self, status, proposals, uncovered, donor_count,
target_feature_count, donors_needed, cover_count,
donor_cap=None, capped_out=()):
self.status = status
self.proposals = tuple(proposals)
# Target feature indexes that NO donor covers, in any combination.
self.uncovered = tuple(uncovered)
# The donor cap this answer was found under, and the features that
# SOME donor covers but the best proposal could not reach inside it.
# Disjoint from `uncovered` by construction: a feature nobody has is
# not a feature the cap kept out.
self.donor_cap = donor_cap
self.capped_out = tuple(capped_out)
self.donor_count = donor_count
self.target_feature_count = target_feature_count
# Size of the smallest cover FOUND. Not proven minimal -- the search
# is greedy; see the note above `_greedy_covers`. There is no
# `minimal` flag because the answer is always the same: no.
self.donors_needed = donors_needed
self.cover_count = cover_count # how many covers of that size exist
@property
def best(self):
return self.proposals[0] if self.proposals else None
def summary(self):
"""One sentence a button can print without knowing the vocabulary."""
n = self.target_feature_count
if self.status == NO_DONORS:
return ('No eligible donors were offered, so nothing was judged. '
'{0} target feature(s) unexamined.'.format(n))
if self.status == NO_COVERAGE:
return ('Judged {0} donor(s): not one of them covers any of the '
'{1} target feature(s).'.format(self.donor_count, n))
k = self.donors_needed
if self.status == CAPPED:
return ('At most {0} donor(s) per proposal; {1} of them cover {2} '
'of {3} target feature(s). {4} feature(s) are covered by '
'some donor but not by any {0}-donor set: {5}.'.format(
self.donor_cap, k, n - len(self.capped_out), n,
len(self.capped_out), list(self.capped_out)))
if self.status == COMPLETE:
return ('{0} donor(s) cover all {1} target feature(s); {2} such '
'cover(s) found, {3} shown.'.format(
k, n, self.cover_count, len(self.proposals)))
tail = ''
if self.capped_out:
tail = (' A further {0} feature(s) are covered by some donor but '
'not within the {1}-donor cap: {2}.'.format(
len(self.capped_out), self.donor_cap,
list(self.capped_out)))
return ('{0} donor(s) cover {1} of {2} target feature(s); {3} '
'feature(s) no donor covers at all: {4}.{5}'.format(
k, n - len(self.uncovered) - len(self.capped_out), n,
len(self.uncovered), list(self.uncovered), tail))
def __repr__(self):
return '<Suggestion {0} donors={1} features={2} proposals={3}>'.format(
self.status, self.donor_count, self.target_feature_count,
len(self.proposals))
# --------------------------------------------------------------------------
# The coverage table. This is the only place `covers` is called.
# --------------------------------------------------------------------------
def _coverage(target_features, donors, covers):
"""Build, for every donor, the best it can do on every target feature.
Returns (best, masks) where
best[d][i] = None, or (donor_feature_index, distance)
masks[d] = bitmask over target feature indexes donor d covers
A donor may carry several features that cover one target feature (two
notches near one blockout, say). The best one wins: lowest distance, and
among equal distances the lowest donor-feature index, so the choice does
not depend on iteration luck.
An exception raised by `covers` is NOT caught. A comparison that blows up
is a bug in the comparison, and swallowing it here would turn it into a
quiet "this donor covers nothing" -- a confident wrong answer, which is
the exact failure mode this project exists to prevent. A contract-breaking
return value (negative, NaN, not a number) is raised on, naming the pair,
for the same reason.
"""
best = []
masks = []
for d_index in range(len(donors)):
donor_features = donors[d_index][1] or []
row = [None] * len(target_features)
mask = 0
for f_index in range(len(donor_features)):
df = donor_features[f_index]
for t_index in range(len(target_features)):
got = covers(df, target_features[t_index])
if got is None:
continue
dist = _checked_distance(got, d_index, f_index, t_index)
current = row[t_index]
if current is None or dist < current[1]:
row[t_index] = (f_index, dist)
mask |= (1 << t_index)
best.append(row)
masks.append(mask)
return best, masks
def _checked_distance(value, d_index, f_index, t_index):
try:
dist = float(value)
except (TypeError, ValueError):
raise ValueError(
'covers() returned a non-number {0!r} for donor {1} feature {2} '
'vs target feature {3}; the contract is None or a float >= 0'
.format(value, d_index, f_index, t_index))
if dist != dist: # NaN, which compares false with itself
raise ValueError(
'covers() returned NaN for donor {0} feature {1} vs target '
'feature {2}'.format(d_index, f_index, t_index))
if dist < 0.0:
raise ValueError(
'covers() returned a negative distance {0!r} for donor {1} '
'feature {2} vs target feature {3}; 0.0 is exact and there is '
'nothing closer than exact'.format(value, d_index, f_index,
t_index))
return dist
# --------------------------------------------------------------------------
# Set cover, by greedy with restarts.
#
# WHY GREEDY, AND WHERE IT IS WRONG.
#
# Set cover is NP-hard, so this does not solve it; it approximates it, and the
# approximation is chosen deliberately over an exhaustive search. A panel
# carries on the order of five to ten features, an exhaustive search over
# subsets of that universe would also be affordable, and the honest reason to
# take greedy anyway is that it is twenty lines instead of a hundred, has no
# recursion, no node budget and no depth cap to get wrong, and can be read in
# one sitting by whoever inherits it. If evidence ever turns up that it is
# picking badly on real panels, replacing it is a local change: everything
# else in this module works off the donor sets it returns.
#
# WHERE IT CAN RETURN A WORSE ANSWER THAN OPTIMAL. Greedy takes the donor that
# covers the most still-uncovered features, and that first bite can be the
# wrong one. Nine features 1..9, and donors
# D = {3, 6, 9} A = {1, 2, 3} B = {4, 5, 6} C = {7, 8, 9}
# Plain greedy takes D first (it is tied for the biggest bite and happens to
# be listed first), and then still needs all three of A, B and C to finish:
# four donors where {A, B, C} covers everything with three. In general greedy
# can return a factor of ln(n) more donors than the minimum, and a suggestion
# that says "you need four donors" when three would do is exactly the kind of
# wrong that gets a tool stopped being used.
#
# Restarting from every possible first donor (below) removes that whole family
# of failures -- the walk seeded from A finds {A, B, C} -- and at five-to-ten
# features it is hard to construct a case that beats it. It is still not a
# proof, there is no guarantee here, and nothing in the result claims one. If
# a real panel ever shows this picking badly, swap `_greedy_covers` for an
# exhaustive search over subsets: the universe is the target's features, which
# is small enough that a complete search is affordable, and everything else in
# this module works off the donor sets that function returns.
#
# THE RESTARTS. Plain greedy gives one answer, and one answer is not a
# suggestion -- the engineer needs to see the second and third options and be
# able to disagree with the ranking. So greedy is run once per donor, each run
# forced to start from that donor and then filling greedily; the resulting
# covers are deduplicated, the smallest size wins (fewest donors is the
# objective), and exactness ranks what is left. It costs D passes over D
# donors, which at these sizes is nothing.
# --------------------------------------------------------------------------
def _popcount(mask):
n = 0
while mask:
mask &= mask - 1
n += 1
return n
def _greedy_fill(masks, full, chosen, covered, max_donors=None):
"""Extend `chosen` greedily until nothing more can be added.
Takes the donor adding the most still-uncovered features; ties go to the
lowest donor index, so the walk is fixed and the whole module stays
reproducible run to run.
`max_donors` stops the walk once the proposal is that big, even if
features remain. That is the owner's cap, and it is enforced HERE rather
than by trimming a finished cover -- trimming would drop whichever donor
happened to be last, while stopping early means every donor in the four
was chosen for being the biggest remaining bite.
"""
chosen = list(chosen)
while covered != full:
if max_donors is not None and len(chosen) >= max_donors:
break
pick = None
gain = 0
for d in range(len(masks)):
if d in chosen:
continue
added = _popcount(masks[d] & ~covered)
if added > gain:
pick, gain = d, added
if pick is None:
break
chosen.append(pick)
covered |= masks[pick]
return tuple(sorted(chosen)), covered
def _greedy_covers(masks, full, max_donors=None):
"""Candidate covers: greedy restarted from every donor that covers anything.
Returns (k, covers, reached) -- the smallest cover size found among the
covers that get FURTHEST, every distinct cover of that size, and the union
of what they reach. `k` is None and `covers` empty when no donor covers
anything.
REACH BEFORE SIZE, and only because of the cap. Without a cap every greedy
walk ends at `full` (it stops only when no donor adds anything, and every
feature in `full` has some donor), so all candidates reach equally far and
this ordering is the old "fewest donors wins" unchanged. With a cap the
walks can end short, and then a two-donor set covering four features must
not beat a four-donor set covering eighteen: covering the target is the
objective, fewest donors is the tie-break under it.
"""
if full == 0:
return None, [], 0
seen = {}
for d in range(len(masks)):
if not masks[d]:
continue
cover, covered = _greedy_fill(masks, full, (d,), masks[d], max_donors)
seen[cover] = covered
if not seen:
return None, [], 0
reach = max([_popcount(m) for m in seen.values()])
best = [c for c in seen if _popcount(seen[c]) == reach]
k = min([len(c) for c in best])
keep = sorted([c for c in best if len(c) == k])
reached = 0
for c in keep:
reached |= seen[c]
return k, keep, reached
# --------------------------------------------------------------------------
# Ranking.
#
# Fewest donors is the objective and it is not negotiable; exactness only
# breaks ties between covers of equal size. Within one size, the question is
# how per-feature distances combine into something comparable.
#
# NOT BY SUM. Summing loses precisely the information the owner's ruling is
# about. A cover that is exact on four features and 2.0 off on the fifth, and
# a cover that is 0.4 off on all five, have the same sum of 2.0 -- so a sum
# calls them equal, when the owner said plainly that the exact shapes go in
# front: "如果有完全一样的形状,那么这个会在推荐的前侧。如果只是相似,那么就排
# 在后面。"
#
# THE KEY IS THE SORTED DISTANCE VECTOR, ASCENDING, COMPARED LEXICOGRAPHICALLY.
# Sort each cover's per-feature distances smallest-first and compare position
# by position. Since exact matches are 0.0 and sort to the front, this makes
# "has more exact features" the first thing that decides, then "its next-best
# feature is closer", and so on down. It is the owner's rule written as
# arithmetic, and it needs no weights, no normalisation and no threshold.
#
# WHAT THAT COSTS, STATED PLAINLY. It is a strict preference for exactness
# over evenness, and it will sometimes look odd. Cover A = [0.0, 5.0] beats
# cover B = [0.01, 0.02], because A is exact on one feature and B is exact on
# none, even though B is obviously the better fit overall. That is the chosen
# behaviour, not an oversight: it is what "完全一样的形状排在前面" means, and
# the alternative (rank by the worst feature, i.e. compare the vectors
# descending) is the "mediocre everywhere wins" rule the brief warns against.
# `Proposal.total_distance` carries the sum anyway, so a UI can show the
# overall figure next to the ranking without it steering the ranking.
#
# THE MASTER TIE-BREAK, AND WHERE IT SITS.
#
# Some donors are marked MASTER by hand in the model, and the owner's ruling
# (2026-08-30) is that those have been REVIEWED: "某一些模板带有 MASTER,给这些
# MASTER 当母版的优先级提升到最高。他们是被审核过的。" It is the only provenance
# signal this feature has -- `TICKET_IS_REINFORCED` says somebody drew bars,
# not that anybody checked them.
#
# Asked directly how it interacts with exactness -- a loosely-fitting MASTER
# against a perfectly exact non-MASTER -- the owner ruled: 拟合更好的排在前面.
# FIT WINS. So MASTER sits BELOW the distance vector and ABOVE input order.
# It cannot reorder anything the distance vector has already separated; it
# decides only between covers the geometry has declared indistinguishable,
# and that is exactly where "who checked this?" is the right question.
#
# Counted, not flagged: a cover with two masters ranks above a cover with one,
# because more of it has been reviewed. Negated in the key so that MORE sorts
# FIRST while everything else still sorts ascending.
#
# THE SAME-ELEVATION TIE-BREAK, AND WHERE IT SITS.
#
# Owner, 2026-09-02, on WP425: five donors tied on every criterion above --
# 1 donor, 5/5 covered, 4 exact, 0 reviewed, 0.04 in total distance, all
# five identical -- and the tie fell through to mark order, so WP133
# printed first though WP234 shares WP425's own building elevation:
# "WP234明明在同一个elevation,为什么排名没有提前?" Ruled the same day,
# approved as briefed ("同意,就这么做"): elevation is an AFFINITY, and it
# sits LAST, below MASTER -- after fewest donors, most exact, most reviewed
# and total distance, and above only the donors' input order. It is ranked
# below MASTER on the same reasoning MASTER is ranked below the distance
# vector: a fact about WHERE a donor sits does not say whether it is a good
# template, and a piece somebody has actually reviewed is worth more than
# one that merely happens to be on the right facade. Nothing here reorders
# what MASTER or the distance vector have already separated -- it only
# decides among covers every criterion above it left dead level, exactly
# the case WP425 hit.
#
# Counted the same way `master_count` is, for the same reason: a cover with
# two same-elevation donors ranks above a cover with one. Negated in the key
# so MORE sorts FIRST.
#
# UNKNOWN IS NEVER A MATCH. `same_elevation_keys` (built by
# `reinforcebind.propose`) never contains a donor whose own elevation could
# not be read, and it is empty outright when the TARGET's elevation could
# not be read -- an unread elevation is could-not-judge, not a wildcard that
# matches everything. See `reinforcecore.elevation_key`.
#
# LAST TIE-BREAK: the donors' input order. Two covers with identical distance
# vectors, master counts and elevation counts are, on the evidence available
# here, equally good, and the matcher has nothing further to say. Falling
# back to input order makes the answer reproducible -- an engineer who runs
# it twice and gets two different answers will not trust it a third time --
# and it is now genuinely the LAST word: mark order, nothing more, no
# remaining lever a caller is expected to pull by reordering donors.
# --------------------------------------------------------------------------
def _proposal_sort_key(proposal):
# `-covered_count` first, and it is a NO-OP everywhere except under the
# donor cap: uncapped, every candidate cover reaches the same feature set,
# so this term is constant and the ranking is exactly what it always was
# (fewest donors, then the distance vector, then MASTER, then same
# elevation, then input order). Capped, it is the difference between
# "eighteen of nineteen" and "four of nineteen, but with only two
# donors".
return (-proposal.covered_count,
len(proposal.donor_indexes),
proposal.distances,
-proposal.master_count,
-proposal.elevation_count,
proposal.donor_indexes)
def _build_proposal(donor_indexes, donors, best, target_count,
master_keys=(), same_elevation_keys=()):
assignment = []
uncovered = []
for t_index in range(target_count):
pick = None
for d_index in donor_indexes:
got = best[d_index][t_index]
if got is None:
continue
if pick is None or got[1] < pick[2]:
pick = (d_index, got[0], got[1])
if pick is None:
uncovered.append(t_index)
assignment.append(pick)
keys = [donors[d][0] for d in donor_indexes]
return Proposal(donor_indexes, keys, assignment, uncovered, master_keys,
same_elevation_keys)
# --------------------------------------------------------------------------
# The entry point.
# --------------------------------------------------------------------------
def suggest(target_features, donors, covers,
max_proposals=DEFAULT_MAX_PROPOSALS, masters=None,
max_donors=DEFAULT_MAX_DONORS, same_elevation=None):
"""Rank the smallest sets of donors that between them cover the target.
target_features -- sequence of opaque feature records for the panel being
reinforced. Order matters only as identity: every index
in the result refers back into this sequence.
donors -- sequence of (key, feature_list) pairs. `key` is opaque
and is only ever used as a label and, at the very end,
as the source of the input order that breaks a dead-even
tie. See the module docstring on why that is a lever.
covers -- the comparison callable. Contract in the module
docstring. This is the only thing in this module that
may read a feature's fields.
masters -- the donor keys that are REVIEWED templates, as any
iterable of keys. Purely a tie-break, ranked below the
distance vector: see the ranking notes above. Keys not
present among `donors` are ignored, and the default of
none is a matcher that ranks exactly as it did before.
same_elevation -- the donor keys that share the TARGET's building
elevation, as any iterable of keys. Purely a
tie-break, ranked below `masters` and above input
order -- see the ranking notes above and
`Proposal.same_elevation`. Keys not present among
`donors` are ignored; the default of none ranks
exactly as before this term existed.
max_proposals -- how many ranked proposals to return. Five by default:
enough that the engineer can see the second and third
options and disagree with the ranking, few enough to
read. `Suggestion.cover_count` reports how many there
actually were.
max_donors -- how many donors ONE proposal may contain. FOUR by the
owner's ruling, and a different axis from
`max_proposals`: this bounds the size of an answer, that
bounds the number of answers. `None` removes the cap.
When four donors cannot reach every coverable feature,
the search returns the best four and names what it could
not reach in `Suggestion.capped_out`, with status
`CAPPED`. It does NOT relax to a fifth. That overflow
rule is inferred, not ruled -- see the module docstring.
Returns a `Suggestion`, or None when the input cannot be judged at all --
that is, when there are no target features. No features means there is
nothing to cover and no basis for any suggestion; that is "could not
judge", and it must not be confused with the empty-but-real results this
returns when donors were examined and found wanting. `Suggestion.status`
separates those: NO_DONORS (nothing eligible was offered, so nothing was
judged) from NO_COVERAGE (donors were examined; none covers anything).
PARTIAL COVERS ARE RETURNED, deliberately. When some feature is covered by
no donor at all, "nothing found" and "here is a cover of nine of your ten
features, and this is the one nobody has" are very different answers, and
only the second is any use: it tells the engineer which detail they have to
reinforce by hand, which is a much shorter job than the whole panel. So the
search runs over the coverable features, the proposals cover all of those,
and `Suggestion.uncovered` names the rest by index. `status` is PARTIAL so
no caller can mistake it for a complete answer.
"""
if not callable(covers):
raise ValueError('suggest() needs a covers(donor_feature, '
'target_feature) callable; got {0!r}'.format(covers))
if not target_features:
return None # could not judge -- see docstring
target_count = len(target_features)
all_features = tuple(range(target_count))
donors = list(donors or [])
master_keys = frozenset(masters or ())
same_elevation_keys = frozenset(same_elevation or ())
if not donors:
return Suggestion(NO_DONORS, [], all_features, 0, target_count,
None, 0)
best, masks = _coverage(target_features, donors, covers)
full = 0
for m in masks:
full |= m
if full == 0:
return Suggestion(NO_COVERAGE, [], all_features, len(donors),
target_count, None, 0)
uncovered = tuple([i for i in all_features if not (full & (1 << i))])
k, cover_sets, _reached = _greedy_covers(masks, full, max_donors)
proposals = [_build_proposal(cs, donors, best, target_count, master_keys,
same_elevation_keys)
for cs in cover_sets]
proposals.sort(key=_proposal_sort_key)
# What the CAP kept out, read off the proposal that will be shown first
# rather than off the union: the engineer acts on the answer he is given.
# A feature nobody has is never counted here -- that is `uncovered`, a
# different finding with a different remedy.
nobody = set(uncovered)
capped_out = tuple([i for i in (proposals[0].uncovered if proposals else ())
if i not in nobody])
if uncovered:
status = PARTIAL
elif capped_out:
status = CAPPED
else:
status = COMPLETE
return Suggestion(status, proposals[:max_proposals], uncovered,
len(donors), target_count, k, len(cover_sets),
donor_cap=max_donors, capped_out=capped_out)
# --------------------------------------------------------------------------
# A reference `covers`, for tests and for reading.
#
# THIS IS NOT THE PRODUCTION COMPARISON and must not be mistaken for one. It
# makes no claim about the index's feature schema -- it operates on a bare
# 4-tuple chosen because it is the smallest thing that can express the worked
# example, and it encodes no precast engineering judgement whatsoever. Its job
# is to be a fixed, legible stand-in so the selection logic above can be tested
# exhaustively before the real comparison exists, and to be an executable
# statement of the contract.
# --------------------------------------------------------------------------
def rect_distance(donor_rect, target_rect):
"""Reference `covers` over rectangles `(x0, x1, y0, y1)`, unit-agnostic.
Coordinates are absolute, measured from the start of the piece -- never a
fraction of its length.
Covers-at-all: each rectangle must contain the other's centre. That is a
two-dimensional test on purpose, and it is why it is written as a mutual
condition rather than a range overlap: in the worked example two donor
features share an x-range with a target feature while sitting in a
different Z band, and a single-axis check calls those the same feature.
They are not.
Distance: the sum of the four absolute corner offsets, IN WHATEVER UNIT
THE COORDINATES ARE IN. Zero for identical rectangles, and one unit for
every kind of feature, as the contract requires.
DELIBERATELY UNIT-AGNOSTIC, unlike the production comparison. This is a
stand-in over bare tuples for testing the selection logic; it declares no
unit because its inputs declare none, and it applies no quantization. The
production `covers` -- `reinforcebind.covers` -- reads feet off index
records and returns INCHES on a 1/8 in grain, which is where the owner's
unit ruling actually lands. Do not read this function as the unit
contract; read requirement 4 above.
"""
try:
dx0, dx1, dy0, dy1 = [float(v) for v in donor_rect]
tx0, tx1, ty0, ty1 = [float(v) for v in target_rect]
except (TypeError, ValueError):
return None # not judgeable -> not covered
def contains(x0, x1, y0, y1, cx, cy):
return x0 <= cx <= x1 and y0 <= cy <= y1
if not contains(dx0, dx1, dy0, dy1, (tx0 + tx1) / 2.0, (ty0 + ty1) / 2.0):
return None
if not contains(tx0, tx1, ty0, ty1, (dx0 + dx1) / 2.0, (dy0 + dy1) / 2.0):
return None
return (abs(dx0 - tx0) + abs(dx1 - tx1) +
abs(dy0 - ty0) + abs(dy1 - ty1))