← Files Aivana Dataverse App ArchitectARCHIVED FILE
scripts/score_app_health.py
2.92 KB · Sep 30, 2026 · 23:15 UTC
#!/usr/bin/env python3
"""Calculate declared planning readiness, not verified release readiness."""
import argparse
import json
from pathlib import Path
from blueprint_schema import load_blueprint
def truth(data, dotted):
value = data
for part in dotted.split("."):
if not isinstance(value, dict):
return False
value = value.get(part)
return bool(value)
def main():
parser = argparse.ArgumentParser(description="Score a Dataverse app blueprint from 0 to 100")
parser.add_argument("blueprint", type=Path)
parser.add_argument("--json", action="store_true", dest="as_json")
args = parser.parse_args()
try:
data = load_blueprint(args.blueprint)
except (OSError, ValueError) as exc:
print(f"ERROR: Cannot read blueprint: {exc}")
return 2
pillars = {
"Business value": (["business.outcome", "business.successMetrics", "business.baselines", "business.targets"], 15),
"Data and security": (["dataModel.tables", "security.leastPrivilegeReviewed", "security.roleTaskMatrix", "security.fieldSecurityReviewed"], 20),
"UX and accessibility": (["personas", "navigation", "forms", "views", "testing.keyboardTest", "testing.responsiveTest"], 20),
"Performance": (["performance.expectedRecordVolume", "performance.formComponentBudget", "performance.representativeDataTest"], 15),
"Change resilience": (["change.impactReviewed", "change.rollbackPlan"], 10),
"ALM and evidence": (["alm.devUnmanaged", "alm.downstreamManaged", "alm.repositoryExport", "alm.version", "testing.roleSmokeTests"], 20),
}
detail, total, actions = [], 0, []
for label, (checks, weight) in pillars.items():
passed = sum(truth(data, check) for check in checks)
points = round(weight * passed / len(checks))
total += points
missing = [check for check in checks if not truth(data, check)]
detail.append({"pillar": label, "score": points, "maximum": weight, "missing": missing})
actions.extend(f"{label}: {item} nachweisen" for item in missing)
deprecated = data.get("modernization", {}).get("deprecatedComponents", [])
if deprecated:
total = max(0, total - min(10, len(deprecated) * 3))
actions.append("Modernisierung: Deprecated Components ablösen oder Risikofreigabe dokumentieren")
result = {"score": total, "releaseBand": "review-required" if total >= 75 else "blocked", "assessmentType": "declared-planning-readiness", "runtimeVerified": False, "pillars": detail, "actions": actions}
if args.as_json:
print(json.dumps(result, ensure_ascii=False, indent=2))
else:
print(f"App Health Score: {total}/100 ({result['releaseBand']})")
for item in detail:
print(f"- {item['pillar']}: {item['score']}/{item['maximum']}")
for action in actions:
print("ACTION: " + action)
return 0
if __name__ == "__main__":
raise SystemExit(main())
SHA-256: ca1b71cb7670e3aaba601057344d1e16ded177ce59eb80440c0a6d5852a0175a