-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheck_consistency.py
More file actions
165 lines (140 loc) · 7.5 KB
/
Copy pathcheck_consistency.py
File metadata and controls
165 lines (140 loc) · 7.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/env python3
"""Consistentiecheck (PROJECTINSTRUCTIE_v4.0.md sectie 16.4).
Vergelijkt alle gepubliceerde prijzen en limieten in de gebouwde output met
config/pricing.json. Draait mee in de build (build_static.main) en als
losstaand script in de maandelijkse onderhoudstaak. De build faalt bij elke
afwijking (exit 1).
Regels:
1. Op commerciele paginas mag elk kaal dollarbedrag (zonder K/M/B-suffix en
zonder duizendtallen-komma) alleen een waarde uit config/pricing.json zijn.
Concurrentprijzen zijn toegestaan mits gemarkeerd met data-competitor-price.
2. Elke vermelding van "N free (MCP) calls" moet exact de limiet uit de config zijn.
3. Geen enkel "TODO_"-placeholder mag in de gepubliceerde output lekken.
4. Elke rapportpagina bevat de juiste checkout-URLs (preview en snapshot) uit de
config (DoD punt 9: preview naar preview, koop naar koop).
5. De kernzin (value_prop, RONDE_FINAL punt 2) staat letterlijk en identiek op
de homepage, de vergelijkingspagina, de pricingpagina en de agents-pagina,
en in elke actor-README (bron voor Apify Store en MCP-documentatie).
Editoriale paginas (blog, guides) worden op regel 2 en 3 gecheckt, niet op 1.
"""
import glob
import json
import os
import re
import sys
ROOT = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(ROOT, "public")
PRICING = json.load(open(os.path.join(ROOT, "config", "pricing.json")))
ALLOWED = {
float(PRICING["tiers"]["free"]["price_usd"]),
float(PRICING["tiers"]["snapshot"]["price_usd"]),
float(PRICING["tiers"]["pro"]["price_month_usd"]),
float(PRICING["tiers"]["pro"]["price_year_usd"]),
float(PRICING["actors"]["price_per_result_usd"]),
}
MCP_FREE = int(PRICING["tiers"]["free"]["mcp_free_calls_per_month"])
MCP_PRO = int(PRICING["tiers"]["pro"]["mcp_calls_per_month"])
# Commerciele oppervlakken: hier geldt de strikte prijsregel.
COMMERCIAL_GLOBS = [
"index.html", "pricing.html", "reports.html", "compare.html", "performance.html",
"datasignals-mcp.html", "reports/*.html", "congress/*.html", "funds/*.html",
"raises/*.html",
]
BARE_PRICE = re.compile(r"\$(\d{1,4}(?:\.\d{1,2})?)(?![\d,])")
SUFFIXED = re.compile(r"\$[\d.,]+\s?[KMB]\b")
COMP_SPAN = re.compile(r"<span[^>]*data-competitor-price[^>]*>.*?</span>", re.S)
FREE_CALLS = re.compile(r"(\d+)\s+free\s+(?:MCP\s+)?calls", re.I)
ANY_CALLS = re.compile(r"(\d+)\s+(?:MCP\s+)?calls\s+(?:per|a)\s+month", re.I)
STRIP_TAGS = re.compile(r"<script[^>]*>.*?</script>|<style[^>]*>.*?</style>", re.S)
def visible(html):
return STRIP_TAGS.sub(" ", html)
def check():
problems = []
if not os.path.isdir(OUT):
return ["public/ ontbreekt; draai build_static.py eerst"]
all_html = glob.glob(os.path.join(OUT, "**", "*.html"), recursive=True)
commercial = set()
for g in COMMERCIAL_GLOBS:
commercial.update(glob.glob(os.path.join(OUT, g)))
for path in all_html:
rel = os.path.relpath(path, OUT)
raw = open(path, encoding="utf-8").read()
body = visible(raw)
# Regel 3: geen TODO-placeholders in output
if "TODO_" in body:
problems.append(f"{rel}: TODO_-placeholder lekt in gepubliceerde output")
# Regel 2: free-calls limiet overal identiek
for m in FREE_CALLS.finditer(body):
if int(m.group(1)) != MCP_FREE:
problems.append(f"{rel}: '{m.group(0)}' wijkt af van limiet {MCP_FREE} uit config/pricing.json")
# Regel 2b: elke 'N calls per maand'-vermelding is de free- of Pro-limiet
for m in ANY_CALLS.finditer(body):
if int(m.group(1)) not in (MCP_FREE, MCP_PRO):
problems.append(f"{rel}: '{m.group(0)}' wijkt af van de limieten "
f"{MCP_FREE}/{MCP_PRO} uit config/pricing.json")
# Regel 1: kale dollarbedragen alleen uit de config (commerciele paginas)
if path in commercial:
scan = COMP_SPAN.sub(" ", body)
scan = SUFFIXED.sub(" ", scan)
for m in BARE_PRICE.finditer(scan):
val = float(m.group(1))
if val not in ALLOWED:
ctx = scan[max(0, m.start() - 40):m.end() + 40].replace("\n", " ")
problems.append(f"{rel}: prijs ${m.group(1)} staat niet in config/pricing.json (context: ...{ctx}...)")
# Regel 4: juiste checkout-links per rapport (preview naar preview, koop naar koop)
for key, r in PRICING["reports"].items():
landing = os.path.join(OUT, r["landing"].lstrip("/"))
if not os.path.exists(landing):
problems.append(f"{r['landing']}: landingspagina ontbreekt in de build")
continue
raw = open(landing, encoding="utf-8").read()
if r["checkout_preview"] not in raw:
problems.append(f"{r['landing']}: preview-checkout-URL uit config ontbreekt")
if r["checkout_snapshot"] not in raw:
problems.append(f"{r['landing']}: snapshot-checkout-URL uit config ontbreekt")
reports_page = os.path.join(OUT, "reports.html")
if os.path.exists(reports_page):
raw = open(reports_page, encoding="utf-8").read()
for key, r in PRICING["reports"].items():
if r["checkout_preview"] not in raw or r["checkout_snapshot"] not in raw:
problems.append(f"reports.html: checkout-URLs van rapport '{key}' ontbreken of wijken af")
# Regel 5: de kernzin letterlijk en identiek op de vier oppervlakken en in
# elke actor-README (een bron: config/pricing.json -> value_prop).
value_prop = PRICING.get("value_prop", "")
if value_prop:
for page in ("index.html", "compare.html", "pricing.html", "datasignals-mcp.html"):
path = os.path.join(OUT, page)
if not os.path.exists(path):
problems.append(f"{page}: pagina ontbreekt in de build (kernzin-check)")
elif value_prop not in open(path, encoding="utf-8").read():
problems.append(f"{page}: kernzin (value_prop) ontbreekt of wijkt af van config/pricing.json")
sys.path.insert(0, os.path.join(ROOT, "scripts"))
from actor_readme_pricing import ACTOR_DIRS
apify_root = os.path.dirname(ROOT)
for d in ACTOR_DIRS:
readme = os.path.join(apify_root, d, "README.md")
if os.path.exists(readme) and value_prop not in open(readme, encoding="utf-8").read():
problems.append(f"{d}/README.md: kernzin (value_prop) ontbreekt of wijkt af van config/pricing.json")
# Regel 6: de vlaggenschip-tool staat als eerste ingang op de MCP-pagina en in
# de machine-leesbare toolslijst. Voorkomt terugval naar de oude flagship.
flagship = "confluence_signals"
mcp_page = os.path.join(OUT, "datasignals-mcp.html")
if os.path.exists(mcp_page) and flagship not in open(mcp_page, encoding="utf-8").read():
problems.append(f"datasignals-mcp.html: vlaggenschip {flagship} ontbreekt op de MCP-pagina")
openapi = os.path.join(OUT, "api", "openapi.json")
if os.path.exists(openapi):
tools = json.load(open(openapi, encoding="utf-8")).get("x-mcp", {}).get("tools", [])
if flagship not in tools:
problems.append(f"api/openapi.json: vlaggenschip {flagship} ontbreekt in x-mcp.tools")
return problems
def main():
problems = check()
if problems:
print("CONSISTENTIECHECK GEFAALD (%d afwijkingen):" % len(problems))
for p in problems:
print(" -", p)
return 1
print("Consistentiecheck geslaagd: alle gepubliceerde prijzen en limieten komen uit config/pricing.json.")
return 0
if __name__ == "__main__":
sys.exit(main())