pi-revit 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,147 +0,0 @@
1
- {
2
- "schema_version": 1,
3
- "description": "Every normative PI-Revit rule and how it is enforced. Guidance sentences carry <!-- inv:<id> --> tags. enforcement=code requires an existing test name (substring of a test title under tests/). A safety rule may be advisory only when it concerns agent intent that code cannot observe; then it must name the agent-eval scenario that measures it. Checked by npm run test:docs.",
4
- "invariants": [
5
- {
6
- "id": "document-identity-guard",
7
- "rule": "Model writes, previews, exports, view activation and selection changes run only against the exact open document identity supplied by the caller.",
8
- "severity": "safety", "enforcement": "code",
9
- "location": ["src/Revit/Tools/DocumentGuard.cs", "src/Revit/ToolRegistry.cs"],
10
- "test": "registry requires exact identity"
11
- },
12
- {
13
- "id": "no-session-fallback",
14
- "rule": "Once a Revit session is selected, calls never silently fall back to another session.",
15
- "severity": "safety", "enforcement": "code",
16
- "location": ["extensions/pi-revit/instance-router.ts"],
17
- "test": "selected bridge removal never falls back to a remaining instance"
18
- },
19
- {
20
- "id": "receipt-routing",
21
- "rule": "Receipt reads and identical retries go to the original bridge session; a retry is never replayed against a different session.",
22
- "severity": "safety", "enforcement": "code",
23
- "location": ["extensions/pi-revit/index.ts", "extensions/pi-revit/instance-router.ts"],
24
- "test": "retry from an earlier bridge generation is rejected before any POST"
25
- },
26
- {
27
- "id": "saved-result-sandbox",
28
- "rule": "read_revit_result resolves only result IDs created in this extension session and never reads arbitrary files.",
29
- "severity": "safety", "enforcement": "code",
30
- "location": ["extensions/pi-revit/index.ts"],
31
- "test": "retrieval rejects unknown IDs and invalid ranges without arbitrary file access"
32
- },
33
- {
34
- "id": "manual-path-containment",
35
- "rule": "Documentation paths are resolved only inside the package; a bridge-provided path is never trusted.",
36
- "severity": "safety", "enforcement": "code",
37
- "location": ["extensions/pi-revit/tool-documentation.ts"],
38
- "test": "realpath containment rejects a manual link escaping its documentation root"
39
- },
40
- {
41
- "id": "no-tool-saves-model",
42
- "rule": "No bridge tool saves, closes or synchronizes the model; a committed transaction is not a file save.",
43
- "severity": "safety", "enforcement": "code",
44
- "location": ["src/Revit/Tools/"],
45
- "test": "architecture: no bridge tool saves, closes or synchronizes a document"
46
- },
47
- {
48
- "id": "parameter-ambiguity",
49
- "rule": "A display-name parameter reference that matches several parameters on one element is never silently resolved to one of them.",
50
- "severity": "safety", "enforcement": "code",
51
- "location": ["src/Revit/Tools/ParameterResolver.cs"],
52
- "test": "ambiguous display name is rejected for writes and filters"
53
- },
54
- {
55
- "id": "missing-not-silent",
56
- "rule": "A filter naming a parameter that is missing on probed elements keeps its warning even when other elements match.",
57
- "severity": "correctness", "enforcement": "code",
58
- "location": ["src/Revit/Tools/GetElements.cs"],
59
- "test": "missing-parameter warning survives other matches"
60
- },
61
- {
62
- "id": "special-objects-flagged",
63
- "rule": "System-owned objects such as titleblock revision schedules are never mixed silently into ordinary results; they are flagged or excluded with a count.",
64
- "severity": "correctness", "enforcement": "code",
65
- "location": ["src/Revit/Tools/ElementTraits.cs", "src/Revit/Tools/ManageSheetPlacements.cs"],
66
- "test": "titleblock revision schedules are flagged in sheet placement listings"
67
- },
68
- {
69
- "id": "derived-state-reported",
70
- "rule": "A tool that creates an object from an existing one (duplicate, copy, mirror, retype) reports the state the object inherited: hidden categories and elements, filters, overrides, template and carried values.",
71
- "severity": "correctness", "enforcement": "code",
72
- "location": ["src/Revit/Tools/InheritedState.cs", "src/Revit/Tools/InheritedState.Summary.cs", "scripts/check-tool-documentation.mjs"],
73
- "test": "architecture: a tool that duplicates, copies or retypes reports inherited state",
74
- "eval_scenario": "modify-duplicate-hidden-view"
75
- },
76
- {
77
- "id": "model-changes-reported",
78
- "rule": "Every call of a tool that can change the model reports the objects it added, modified and deleted, and the visibility state of new views, without tool-specific code.",
79
- "severity": "correctness", "enforcement": "code",
80
- "location": ["src/Revit/Tools/ModelChanges.cs", "src/Revit/Tools/ChangeSet.cs", "src/Revit/BridgeServer.cs"],
81
- "test": "preview rollback leaves no net change"
82
- },
83
- {
84
- "id": "existing-objects-not-reused",
85
- "rule": "An object that existed before the request is not edited, reused, replaced or deleted in place of one the request asks to create: names already in use are rejected with the existing object's identity, a change to a pre-existing object the request names is flagged, and the agent asks or reports.",
86
- "severity": "safety", "enforcement": "code",
87
- "location": ["src/Revit/Tools/ElementNames.cs", "extensions/pi-revit/scope-monitor.ts", "extensions/pi-revit/platform-prompt.ts"],
88
- "test": "a write to a pre-existing object named in the request adds a scope note",
89
- "eval_scenario": "modify-name-collision"
90
- },
91
- {
92
- "id": "document-kind-declared",
93
- "rule": "Every bridge tool declares the document kinds it works in (project, family); the bridge refuses any other kind before the tool runs, naming the route to use instead.",
94
- "severity": "correctness", "enforcement": "code",
95
- "location": ["src/Revit/Tools/ToolContract.cs", "src/Revit/Tools/DocumentGuard.cs", "src/Revit/BridgeServer.cs", "scripts/check-tool-documentation.mjs"],
96
- "test": "a project-only tool refuses a family document with the family API route"
97
- },
98
- {
99
- "id": "limits-have-alternatives",
100
- "rule": "Every declared tool limit names an alternative route, and discovery without a full match returns the capability route instead of an unexplained absence.",
101
- "severity": "correctness", "enforcement": "code",
102
- "location": ["src/Revit/Tools/ToolContract.cs", "extensions/pi-revit/tool-catalog.ts", "scripts/check-tool-documentation.mjs"],
103
- "test": "unmatched query returns the capability route"
104
- },
105
- {
106
- "id": "linked-ids-stay-linked",
107
- "rule": "Linked-element IDs identify elements in their linked document and must not be passed to host write or selection tools.",
108
- "severity": "safety", "enforcement": "advisory",
109
- "reason": "A linked element ID is an integer that can also exist in the host; the bridge cannot tell which document the caller meant.",
110
- "eval_scenario": "linked-ids-not-used-for-host-writes"
111
- },
112
- {
113
- "id": "identity-refresh-intent",
114
- "rule": "After an identity rejection, establish that the intended model is active before refreshing the ID; never adopt another model's ID to get past the guard.",
115
- "severity": "safety", "enforcement": "advisory",
116
- "reason": "The guard rejects mismatches, but only the agent knows which model the user meant.",
117
- "eval_scenario": "stale-identity-recovery"
118
- },
119
- {
120
- "id": "uncertain-outcome",
121
- "rule": "An unknown receipt, unavailable bridge or restart does not prove an action never ran; inspect state before any new modification.",
122
- "severity": "safety", "enforcement": "advisory",
123
- "reason": "Whether a new call is a duplicate depends on the agent's reading of state the bridge no longer holds.",
124
- "eval_scenario": "timeout-recovery"
125
- },
126
- {
127
- "id": "capability-claims-checked",
128
- "rule": "Say an operation is not possible only after checking dedicated tools, declared limits and the API route, naming what was checked.",
129
- "severity": "correctness", "enforcement": "advisory",
130
- "reason": "Agent reasoning; supported by the platform protocol and discovery fallback, measured by evaluation.",
131
- "eval_scenario": "boundary-manual-limit"
132
- },
133
- {
134
- "id": "completion-proportionate",
135
- "rule": "Do only the requested change, verify it with the declared method, then stop and report; offer further improvements instead of making them.",
136
- "severity": "correctness", "enforcement": "advisory",
137
- "reason": "Agent behavior; the extension steers repeated verify-and-edit loops but cannot judge the request.",
138
- "eval_scenario": "modify-perspective-view"
139
- },
140
- {
141
- "id": "workspace-root-clean",
142
- "rule": "Never create files loose in the workspace root.",
143
- "severity": "guidance", "enforcement": "advisory",
144
- "reason": "Workspace convention for the agent's own file writes."
145
- }
146
- ]
147
- }
@@ -1,322 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """Reproducible benchmark of pi-revit search_api_docs against RevitAPI.xml ground truth.
3
-
4
- Usage: python scripts/benchmark-search-docs.py [results.json] [path-to-RevitAPI.xml]
5
- Requires: Revit running with the pi-revit bridge add-in loaded (a document need not
6
- be open) and Python 3.8+. Ground truth is Autodesk's own RevitAPI.xml, so every
7
- number is reproducible on any machine with the same Revit version.
8
-
9
- Categories:
10
- A exact-name recall/ranking B signature + spacing variants
11
- C complex syntax (arrays, nested generics, ref/out, ctors)
12
- D namespace ambiguity E honesty/adversarial controls
13
- F documentation fidelity G latency stats
14
- """
15
- import glob, json, os, random, re, sys, time, urllib.request, xml.etree.ElementTree as ET
16
- from collections import defaultdict
17
-
18
- random.seed(20260722) # reproducible sampling
19
-
20
- APPDATA = os.environ.get("APPDATA") or os.path.expanduser(r"~\AppData\Roaming")
21
- BRIDGE = json.load(open(os.path.join(APPDATA, "RevitBridge", "bridge.json")))
22
- BASE, TOKEN = BRIDGE["baseUrl"], BRIDGE["token"]
23
- if len(sys.argv) > 2:
24
- XML_PATH = sys.argv[2]
25
- else:
26
- revit_version = BRIDGE.get("revitVersion", "")
27
- candidates = glob.glob(rf"C:\Program Files\Autodesk\Revit {revit_version}*\RevitAPI.xml") \
28
- or glob.glob(r"C:\Program Files\Autodesk\Revit 20*\RevitAPI.xml")
29
- if not candidates:
30
- sys.exit("RevitAPI.xml not found; pass its path as the second argument.")
31
- XML_PATH = candidates[-1]
32
-
33
- LAT = []
34
- def search(query, max_results=10, kind=None):
35
- args = {"query": query, "max_results": max_results}
36
- if kind: args["kind"] = kind
37
- body = json.dumps(args).encode("utf-8")
38
- url = f"{BASE}/tools/search_api_docs/execute?token={TOKEN}&timeout_ms=60000"
39
- req = urllib.request.Request(url, data=body, headers={"content-type": "application/json"}, method="POST")
40
- t0 = time.perf_counter()
41
- with urllib.request.urlopen(req, timeout=90) as resp:
42
- payload = json.load(resp)
43
- LAT.append((time.perf_counter() - t0) * 1000)
44
- det = (payload.get("details") or {}).get("payload") or {}
45
- return det.get("matches") or [], det.get("totalMatches", 0)
46
-
47
- # ---------------------------------------------------------------- ground truth
48
- PRIM = {"System.String": "string", "System.Boolean": "bool", "System.Double": "double",
49
- "System.Int32": "int", "System.Int64": "long", "System.Object": "object",
50
- "System.Byte": "byte", "System.Single": "float", "System.UInt32": "uint"}
51
-
52
- def split_top(params): # split on commas outside {}
53
- out, depth, cur = [], 0, ""
54
- for ch in params:
55
- if ch == "{": depth += 1
56
- elif ch == "}": depth -= 1
57
- if ch == "," and depth == 0: out.append(cur); cur = ""
58
- else: cur += ch
59
- if cur: out.append(cur)
60
- return out
61
-
62
- def short_type(t):
63
- t = t.strip()
64
- byref = t.endswith("@")
65
- if byref: t = t[:-1]
66
- array = ""
67
- while t.endswith("[]"): array += "[]"; t = t[:-2]
68
- if t in PRIM: base = PRIM[t]
69
- elif "{" in t:
70
- outer, inner = t[:t.index("{")], t[t.index("{")+1:t.rindex("}")]
71
- outer = outer.split(".")[-1].split("`")[0]
72
- base = f"{outer}<{', '.join(short_type(x) for x in split_top(inner))}>"
73
- else:
74
- base = t.split(".")[-1]
75
- return base + array + ("&" if byref else "")
76
-
77
- class Member:
78
- __slots__ = ("kind","full","typename","member","params","summary","pnames","since")
79
- def __init__(s, kind, full, typename, member, params, summary, pnames, since):
80
- s.kind, s.full, s.typename, s.member = kind, full, typename, member
81
- s.params, s.summary, s.pnames, s.since = params, summary, pnames, since
82
- @property
83
- def short_sig_prefix(s):
84
- shorts = [short_type(p) for p in s.params]
85
- head = s.typename if s.member == "#ctor" else f"{s.typename}.{s.member}"
86
- return f"{head}(" + ", ".join(shorts)
87
-
88
- def text_of(el):
89
- if el is None: return None
90
- return re.sub(r"\s+", " ", "".join(el.itertext())).strip() or None
91
-
92
- print("parsing RevitAPI.xml ...", flush=True)
93
- root = ET.parse(XML_PATH).getroot()
94
- methods, props, types = [], [], []
95
- for m in root.iter("member"):
96
- name = m.get("name", "")
97
- summary = text_of(m.find("summary"))
98
- since = text_of(m.find("since"))
99
- pnames = [p.get("name") for p in m.findall("param")]
100
- if name.startswith("M:"):
101
- body = name[2:]
102
- params = ""
103
- if "(" in body:
104
- body, params = body[:body.index("(")], body[body.index("(")+1:body.rindex(")")]
105
- parts = body.split(".")
106
- if len(parts) < 2: continue
107
- member, typename = parts[-1], parts[-2]
108
- if "`" in typename or "`" in member: continue
109
- full = ".".join(parts)
110
- methods.append(Member("M", full, typename, member, split_top(params) if params else [], summary, pnames, since))
111
- elif name.startswith("P:"):
112
- parts = name[2:].split(".")
113
- if len(parts) < 2 or "`" in parts[-2]: continue
114
- props.append(Member("P", name[2:], parts[-2], parts[-1], [], summary, pnames, since))
115
- elif name.startswith("T:"):
116
- parts = name[2:].split(".")
117
- if "`" in parts[-1]: continue
118
- types.append(Member("T", name[2:], parts[-1], "", [], summary, pnames, since))
119
-
120
- print(f"golden pool: {len(methods)} methods, {len(props)} properties, {len(types)} types", flush=True)
121
-
122
- by_ns = defaultdict(list)
123
- for mm in methods: by_ns[".".join(mm.full.split(".")[:-2])].append(mm)
124
- real_methods = {(m.typename, m.member) for m in methods}
125
- overload_count = defaultdict(int)
126
- for m in methods: overload_count[(m.typename, m.member)] += 1
127
-
128
- def stratified(pool, n):
129
- groups = defaultdict(list)
130
- for m in pool: groups[".".join(m.full.split(".")[:4])].append(m)
131
- out, keys = [], sorted(groups)
132
- while len(out) < n and keys:
133
- for k in list(keys):
134
- if groups[k]: out.append(groups[k].pop(random.randrange(len(groups[k]))))
135
- else: keys.remove(k)
136
- if len(out) >= n: break
137
- return out
138
-
139
- R = {} # results
140
-
141
- # ---------------------------------------------------------- A: exact-name recall
142
- A = {"n":0,"top1":0,"top3":0,"recall":0}
143
- sampleA = stratified(methods, 90) + stratified(props, 40) + stratified(types, 30)
144
- for m in sampleA:
145
- q = f"{m.typename}.{m.member}" if m.kind in "MP" else m.typename
146
- matches, _ = search(q)
147
- target = m.full if m.kind in "MP" else m.full
148
- names = [x.get("name","") for x in matches]
149
- A["n"] += 1
150
- A["recall"] += any(n == target for n in names)
151
- A["top1"] += bool(names and names[0] == target)
152
- A["top3"] += any(n == target for n in names[:3])
153
- print("A done", A, flush=True)
154
- R["A exact-name"] = A
155
-
156
- # ------------------------------------------------- B: signatures + spacing variants
157
- B = {"n":0,"top1_sig":0,"variants_agree":0,"variant_sets":0}
158
- sigpool = [m for m in methods if 2 <= len(m.params) <= 5]
159
- sampleB = stratified(sigpool, 60)
160
- for m in sampleB:
161
- try: prefix = m.short_sig_prefix
162
- except Exception: continue
163
- canonical = prefix # "Type.Member(P1, P2..."
164
- matches, _ = search(canonical)
165
- B["n"] += 1
166
- sigs = [x.get("signature","") for x in matches]
167
- want = f"new {m.typename}(" if m.member == "#ctor" else f"{m.typename}.{m.member}("
168
- ok = bool(sigs and sigs[0].startswith(want))
169
- B["top1_sig"] += ok
170
- variants = [canonical.replace(", ", ","),
171
- canonical.replace(", ", " , "),
172
- canonical.replace(", ", ", "),
173
- canonical.replace("(", "( ", 1)]
174
- counts = {len(matches)}
175
- for v in variants:
176
- vm, _ = search(v)
177
- counts.add(len(vm))
178
- B["variant_sets"] += 1
179
- B["variants_agree"] += (len(counts) == 1)
180
- print("B done", B, flush=True)
181
- R["B signature+spacing"] = B
182
-
183
- # --------------------------------------------------------- C: complex syntax shapes
184
- C = {"cases": []}
185
- def probe(label, q, expect_nonzero=True, kind=None):
186
- try: matches, total = search(q, kind=kind)
187
- except Exception as e:
188
- C["cases"].append((label, q, f"ERROR {e}")); return
189
- ok = (total > 0) if expect_nonzero else (total == 0)
190
- top = matches[0]["signature"] if matches else "-"
191
- C["cases"].append((label, q, f"{'OK' if ok else 'MISS'} total={total} top={top[:70]}"))
192
-
193
- arr = next((m for m in methods if any(p.endswith("[]") for p in m.params)), None)
194
- if arr: probe("array param", arr.short_sig_prefix)
195
- nest = next((m for m in methods if any("{" in p and "{" in p[p.index("{")+1:] for p in m.params)), None)
196
- if nest: probe("nested generic", nest.short_sig_prefix)
197
- byref = next((m for m in methods if any(p.endswith("@") for p in m.params)), None)
198
- if byref: probe("ref/out param", byref.short_sig_prefix)
199
- ctor = next((m for m in methods if m.member == "#ctor" and m.typename == "FilteredElementCollector"), None)
200
- probe("constructor", "FilteredElementCollector(Document")
201
- probe("property", "PDFExportOptions.FileName")
202
- probe("fully qualified type", "Autodesk.Revit.DB.Document")
203
- probe("case-insensitive", "wall.create(document, curve")
204
- probe("Double vs double", "UnitUtils.ConvertToInternalUnits(Double, ForgeTypeId")
205
- probe("trailing garbage brackets", "Wall.Create(Document, Curve]]", True) # observational
206
- probe("double periods", "Wall..Create", True) # observational
207
- # 0.2.11: CLR alias + namespace reduction in signature queries, Creation-factory rewrite
208
- probe("CLR alias Boolean", "Wall.Create(Document, Curve, ElementId, Boolean")
209
- probe("qualified param type", "Wall.Create(Autodesk.Revit.DB.Document, Curve")
210
- probe("System-qualified alias", "ParameterFilterElement.Create(Document, System.String")
211
- probe("factory rewrite", "Document.Create.NewRoom(Level, UV")
212
- probe("factory inherited member", "Document.Create.NewFamilyInstance")
213
- probe("factory honesty control", "Document.Create.Banana", expect_nonzero=False)
214
- # 0.2.14: C# accessor spelling (get_X/set_X) resolves to the documented property/indexer
215
- probe("accessor indexer", "Element.get_Parameter(BuiltInParameter")
216
- probe("accessor property", "Element.get_BoundingBox(View")
217
- probe("literal get_ member", "LocationCurve.get_ElementsAtJoin")
218
- # 0.2.16: kind=method on an accessor query admits the documented property
219
- probe("accessor with kind=method", "Element.get_Parameter(BuiltInParameter", kind="method")
220
- probe("kind=method control (real method)", "Wall.Create(Document, Curve", kind="method")
221
- print("C done", flush=True)
222
- R["C complex-syntax"] = C
223
-
224
- # ------------------------------------------------------------ D: namespace ambiguity
225
- D = {"cases": []}
226
- def ns_case(q, want_substr):
227
- matches, total = search(q)
228
- top = matches[0]["name"] if matches else "-"
229
- D["cases"].append((q, want_substr, top, "OK" if want_substr in top else "MISS"))
230
- for q, want in [
231
- ("Autodesk.Revit.DB.Architecture.Room", ".Architecture.Room"),
232
- ("Autodesk.Revit.DB.Mechanical.Space", ".Mechanical.Space"),
233
- ("Architecture.Room", ".Architecture.Room"),
234
- ("Mechanical.Duct", ".Mechanical.Duct"),
235
- ("Plumbing.Pipe", ".Plumbing.Pipe"),
236
- ("Electrical.Wire", ".Electrical.Wire"),
237
- ("Structure.Rebar", ".Structure.Rebar"),
238
- ("Room", "Room"), ("Space", "Space"), ("Area", "Area"),
239
- ("Connector", "Connector"),
240
- ]: ns_case(q, want)
241
- print("D done", flush=True)
242
- R["D namespace"] = D
243
-
244
- # ------------------------------------------------------- E: honesty / adversarial
245
- E = {"n":0,"exact_fp":0,"zero":0,"kinds":defaultdict(lambda:[0,0,0])}
246
- adv = []
247
- pool = stratified([m for m in methods if 1 <= len(m.params) <= 4 and m.member != "#ctor"], 120)
248
- for m in pool[:30]: # misspelled member (drop 2nd letter)
249
- if len(m.member) < 5: continue
250
- adv.append(("misspelled-member", f"{m.typename}.{m.member[0]+m.member[2:]}", m))
251
- for m in pool[30:60]: # nonsense parameter
252
- adv.append(("nonsense-param", f"{m.typename}.{m.member}(Banana", m))
253
- for m in pool[60:90]: # wrong declaring class
254
- other = random.choice(types).typename
255
- if (other, m.member) in real_methods: continue
256
- adv.append(("wrong-class", f"{other}.{m.member}", (other, m.member)))
257
- for m in pool[90:120]: # swapped params (only if that order is not a real overload)
258
- if len(m.params) < 2 or m.params[0] == m.params[1]: continue
259
- sw = [m.params[1], m.params[0]] + m.params[2:]
260
- exists = any(o.params[:len(sw)] == sw for o in methods if (o.typename,o.member)==(m.typename,m.member))
261
- if exists: continue
262
- shorts = [short_type(p) for p in sw]
263
- adv.append(("swapped-params", f"{m.typename}.{m.member}(" + ", ".join(shorts), m))
264
- for kind, q, _meta in adv:
265
- matches, total = search(q)
266
- E["n"] += 1
267
- zero = (total == 0)
268
- # exact false positive: a returned entry CLAIMS the impossible thing exists
269
- fp = False
270
- if kind == "wrong-class":
271
- t, mem = _meta
272
- fp = any(x.get("name","").endswith(f".{t}.{mem}") or f".{t}." in x.get("name","") and x.get("name","").endswith(f".{mem}") for x in matches)
273
- elif kind == "swapped-params":
274
- fp = any(x.get("signature","").lower() == (q.lower()+")") for x in matches)
275
- elif kind == "nonsense-param":
276
- fp = any("banana" in x.get("signature","").lower() for x in matches)
277
- elif kind == "misspelled-member":
278
- bad = q.split(".")[-1].lower()
279
- fp = any(x.get("name","").lower().endswith("."+bad) for x in matches)
280
- E["exact_fp"] += fp
281
- E["zero"] += zero
282
- k = E["kinds"][kind]; k[0] += 1; k[1] += fp; k[2] += zero
283
- print("E done", {k: list(v) for k, v in E["kinds"].items()}, flush=True)
284
- E["kinds"] = {k: list(v) for k, v in E["kinds"].items()}
285
- R["E honesty"] = E
286
-
287
- # ------------------------------------------------------------- F: doc fidelity
288
- F = {"n":0,"param_names_ok":0,"summary_ok":0,"since_ok":0,"since_present":0,"overload_docs_ok":0,"overload_checked":0}
289
- fsample = stratified([m for m in methods if m.pnames and m.summary and m.member != "#ctor"], 50)
290
- for m in fsample:
291
- try: q = m.short_sig_prefix
292
- except Exception: continue
293
- matches, _ = search(q)
294
- if not matches: continue
295
- top = matches[0]
296
- F["n"] += 1
297
- got_pnames = [p.get("name") for p in (top.get("parameters") or [])]
298
- F["param_names_ok"] += (got_pnames == m.pnames)
299
- gs, ws = (top.get("summary") or ""), m.summary
300
- F["summary_ok"] += (re.sub(r"\W","",gs)[:80] == re.sub(r"\W","",ws)[:80])
301
- if m.since:
302
- F["since_present"] += 1
303
- F["since_ok"] += (top.get("since") == m.since)
304
- # overload-doc attribution: right param COUNT on the top signature
305
- if overload_count[(m.typename, m.member)] > 1:
306
- F["overload_checked"] += 1
307
- sig = top.get("signature","")
308
- inner = sig[sig.index("(")+1:sig.rindex(")")] if "(" in sig else ""
309
- F["overload_docs_ok"] += (len(split_top(inner)) == len(m.params) if inner else len(m.params)==0)
310
- print("F done", F, flush=True)
311
- R["F fidelity"] = F
312
-
313
- # ------------------------------------------------------------------ G: latency
314
- LAT.sort()
315
- def pct(p): return LAT[min(len(LAT)-1, int(len(LAT)*p))]
316
- R["G latency"] = {"queries": len(LAT), "cold_first_ms": round(LAT[-1] if not LAT else None or 0),
317
- "p50_ms": round(pct(0.50)), "p95_ms": round(pct(0.95)), "p99_ms": round(pct(0.99)),
318
- "max_ms": round(LAT[-1])}
319
-
320
- json.dump(R, open(sys.argv[1] if len(sys.argv)>1 else "benchmark-results.json","w"), indent=1, default=str)
321
- print("ALL DONE", flush=True)
322
- print(json.dumps(R, indent=1, default=str)[:3000])