Contains: - 1m-brag - tem - VaultMesh_Catalog_v1 - VAULTMESH-ETERNAL-PATTERN 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
425 lines
16 KiB
Python
425 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
PQC Integration Budget & Person-Month Checker
|
|
|
|
Purpose:
|
|
Validates consortium budget and person-month allocations from consortium-tracker.csv
|
|
against PQC Integration proposal constraints:
|
|
- Total budget: €2,800,000 (€2.8M)
|
|
- Total person-months: 104 PM baseline (112 PM with 10% buffer)
|
|
- Budget distribution: VaultMesh 70.4%, Brno 10%, Cyber Trust 12.5%, France 7.1%
|
|
|
|
Usage:
|
|
python3 budget_checker.py
|
|
|
|
Expected CSV structure (from consortium-tracker.csv):
|
|
Partner Name, Country, Type, Budget (EUR), Person-Months, LOI Status, ...
|
|
|
|
Author: VaultMesh Technologies B.V.
|
|
Version: 1.0
|
|
Date: 2025-11-06
|
|
"""
|
|
|
|
import csv
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Dict, List, Tuple
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
|
|
|
|
class CheckStatus(Enum):
|
|
"""Status codes for validation checks."""
|
|
PASS = "✓ PASS"
|
|
WARN = "⚠ WARN"
|
|
FAIL = "✗ FAIL"
|
|
|
|
|
|
@dataclass
|
|
class PartnerAllocation:
|
|
"""Partner budget and person-month allocation."""
|
|
name: str
|
|
country: str
|
|
partner_type: str
|
|
budget_eur: int
|
|
person_months: float
|
|
loi_status: str
|
|
budget_pct: float = 0.0
|
|
pm_fte_avg: float = 0.0
|
|
|
|
|
|
@dataclass
|
|
class ValidationResult:
|
|
"""Result of a validation check."""
|
|
check_name: str
|
|
status: CheckStatus
|
|
expected: str
|
|
actual: str
|
|
details: str = ""
|
|
|
|
|
|
class BudgetChecker:
|
|
"""Validates PQC Integration budget and person-month allocations."""
|
|
|
|
# Proposal constraints
|
|
TOTAL_BUDGET_EUR = 2_800_000 # €2.8M total
|
|
BASELINE_PM = 104 # Baseline person-months
|
|
BUFFERED_PM = 112 # With 10% buffer
|
|
PROJECT_MONTHS = 24 # 24-month duration
|
|
|
|
# Expected budget distribution (from PQC_Submission_Checklist.md)
|
|
EXPECTED_BUDGET_PCT = {
|
|
"VaultMesh Technologies B.V.": 70.4,
|
|
"Masaryk University": 10.0,
|
|
"Cyber Trust S.A.": 12.5,
|
|
"Public Digital Services Agency": 7.1,
|
|
}
|
|
|
|
# Tolerances
|
|
BUDGET_TOLERANCE_PCT = 2.0 # ±2% tolerance for budget distribution
|
|
PM_TOLERANCE_PCT = 5.0 # ±5% tolerance for person-months
|
|
|
|
def __init__(self, csv_path: Path):
|
|
"""Initialize checker with path to consortium tracker CSV."""
|
|
self.csv_path = csv_path
|
|
self.partners: List[PartnerAllocation] = []
|
|
self.results: List[ValidationResult] = []
|
|
|
|
def load_csv(self) -> bool:
|
|
"""Load partner data from CSV file."""
|
|
if not self.csv_path.exists():
|
|
print(f"✗ ERROR: CSV file not found: {self.csv_path}")
|
|
return False
|
|
|
|
try:
|
|
with open(self.csv_path, 'r', encoding='utf-8') as f:
|
|
reader = csv.DictReader(f)
|
|
for row in reader:
|
|
# Only process rows for PQC Integration proposal
|
|
# CSV uses "Proposal Track" column
|
|
if 'PQC' not in row.get('Proposal Track', ''):
|
|
continue
|
|
|
|
# Parse budget (remove € symbol and commas)
|
|
budget_str = row.get('Budget (€)', '0').replace('€', '').replace(',', '').strip()
|
|
try:
|
|
budget = int(budget_str) if budget_str else 0
|
|
except ValueError:
|
|
print(f"⚠ WARNING: Invalid budget for {row.get('Partner Name')}: {budget_str}")
|
|
budget = 0
|
|
|
|
# Parse person-months
|
|
pm_str = row.get('Person-Months', '0').strip()
|
|
try:
|
|
pm = float(pm_str) if pm_str else 0.0
|
|
except ValueError:
|
|
print(f"⚠ WARNING: Invalid person-months for {row.get('Partner Name')}: {pm_str}")
|
|
pm = 0.0
|
|
|
|
partner = PartnerAllocation(
|
|
name=row.get('Partner Name', 'Unknown').strip(),
|
|
country=row.get('Country', 'Unknown').strip(),
|
|
partner_type=row.get('Type', 'Unknown').strip(),
|
|
budget_eur=budget,
|
|
person_months=pm,
|
|
loi_status=row.get('LOI Status', 'Unknown').strip(),
|
|
)
|
|
self.partners.append(partner)
|
|
|
|
if not self.partners:
|
|
print("✗ ERROR: No PQC Integration partners found in CSV")
|
|
return False
|
|
|
|
print(f"✓ Loaded {len(self.partners)} partners from {self.csv_path.name}\n")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"✗ ERROR loading CSV: {e}")
|
|
return False
|
|
|
|
def calculate_totals(self) -> Tuple[int, float]:
|
|
"""Calculate total budget and person-months."""
|
|
total_budget = sum(p.budget_eur for p in self.partners)
|
|
total_pm = sum(p.person_months for p in self.partners)
|
|
|
|
# Calculate percentages and FTE averages
|
|
for partner in self.partners:
|
|
partner.budget_pct = (partner.budget_eur / total_budget * 100) if total_budget > 0 else 0.0
|
|
partner.pm_fte_avg = partner.person_months / self.PROJECT_MONTHS
|
|
|
|
return total_budget, total_pm
|
|
|
|
def check_total_budget(self, actual_budget: int) -> ValidationResult:
|
|
"""Validate total budget against proposal constraint."""
|
|
expected = f"€{self.TOTAL_BUDGET_EUR:,}"
|
|
actual = f"€{actual_budget:,}"
|
|
|
|
if actual_budget == self.TOTAL_BUDGET_EUR:
|
|
status = CheckStatus.PASS
|
|
details = "Budget matches proposal exactly"
|
|
elif abs(actual_budget - self.TOTAL_BUDGET_EUR) / self.TOTAL_BUDGET_EUR * 100 < self.BUDGET_TOLERANCE_PCT:
|
|
status = CheckStatus.WARN
|
|
variance_pct = (actual_budget - self.TOTAL_BUDGET_EUR) / self.TOTAL_BUDGET_EUR * 100
|
|
details = f"Budget variance: {variance_pct:+.1f}% (within tolerance)"
|
|
else:
|
|
status = CheckStatus.FAIL
|
|
variance = actual_budget - self.TOTAL_BUDGET_EUR
|
|
details = f"Budget off by €{variance:,} ({variance/self.TOTAL_BUDGET_EUR*100:+.1f}%)"
|
|
|
|
return ValidationResult(
|
|
check_name="Total Budget",
|
|
status=status,
|
|
expected=expected,
|
|
actual=actual,
|
|
details=details
|
|
)
|
|
|
|
def check_total_person_months(self, actual_pm: float) -> ValidationResult:
|
|
"""Validate total person-months against baseline/buffered targets."""
|
|
expected = f"{self.BASELINE_PM} PM (baseline) / {self.BUFFERED_PM} PM (buffered)"
|
|
actual = f"{actual_pm:.1f} PM"
|
|
|
|
if self.BASELINE_PM <= actual_pm <= self.BUFFERED_PM:
|
|
status = CheckStatus.PASS
|
|
details = f"Within baseline-buffered range ({actual_pm/self.PROJECT_MONTHS:.1f} FTE avg)"
|
|
elif actual_pm < self.BASELINE_PM:
|
|
status = CheckStatus.WARN
|
|
shortage = self.BASELINE_PM - actual_pm
|
|
details = f"Below baseline by {shortage:.1f} PM (may underdeliver)"
|
|
else:
|
|
status = CheckStatus.FAIL
|
|
excess = actual_pm - self.BUFFERED_PM
|
|
details = f"Exceeds buffer by {excess:.1f} PM (over budget risk)"
|
|
|
|
return ValidationResult(
|
|
check_name="Total Person-Months",
|
|
status=status,
|
|
expected=expected,
|
|
actual=actual,
|
|
details=details
|
|
)
|
|
|
|
def check_budget_distribution(self) -> List[ValidationResult]:
|
|
"""Validate per-partner budget percentages against expected distribution."""
|
|
results = []
|
|
|
|
for partner in self.partners:
|
|
# Find expected percentage (match by partner name prefix)
|
|
expected_pct = None
|
|
for expected_name, pct in self.EXPECTED_BUDGET_PCT.items():
|
|
if expected_name in partner.name or partner.name in expected_name:
|
|
expected_pct = pct
|
|
break
|
|
|
|
if expected_pct is None:
|
|
results.append(ValidationResult(
|
|
check_name=f"Budget % ({partner.name})",
|
|
status=CheckStatus.WARN,
|
|
expected="N/A",
|
|
actual=f"{partner.budget_pct:.1f}%",
|
|
details="Partner not in expected distribution list"
|
|
))
|
|
continue
|
|
|
|
# Check if actual matches expected within tolerance
|
|
variance = abs(partner.budget_pct - expected_pct)
|
|
|
|
if variance < self.BUDGET_TOLERANCE_PCT:
|
|
status = CheckStatus.PASS
|
|
details = f"Matches expected ({variance:.1f}% variance)"
|
|
elif variance < self.BUDGET_TOLERANCE_PCT * 2:
|
|
status = CheckStatus.WARN
|
|
details = f"Slightly off ({variance:.1f}% variance, {partner.budget_pct - expected_pct:+.1f}%)"
|
|
else:
|
|
status = CheckStatus.FAIL
|
|
details = f"Significant deviation ({variance:.1f}% variance, {partner.budget_pct - expected_pct:+.1f}%)"
|
|
|
|
results.append(ValidationResult(
|
|
check_name=f"Budget % ({partner.name})",
|
|
status=status,
|
|
expected=f"{expected_pct:.1f}%",
|
|
actual=f"{partner.budget_pct:.1f}%",
|
|
details=details
|
|
))
|
|
|
|
return results
|
|
|
|
def check_loi_status(self) -> List[ValidationResult]:
|
|
"""Validate LOI status for all partners."""
|
|
results = []
|
|
|
|
for partner in self.partners:
|
|
expected = "Confirmed/Signed/Sent/Coordinator"
|
|
actual = partner.loi_status
|
|
|
|
if actual.lower() in ['confirmed', 'signed', 'sent', 'coordinator']:
|
|
status = CheckStatus.PASS
|
|
details = "LOI confirmed" if actual.lower() != 'coordinator' else "Coordinator (no LOI needed)"
|
|
elif actual.lower() in ['draft', 'pending']:
|
|
status = CheckStatus.WARN
|
|
details = "LOI not yet confirmed (follow up needed)"
|
|
else:
|
|
status = CheckStatus.FAIL
|
|
details = f"LOI status unclear: {actual}"
|
|
|
|
results.append(ValidationResult(
|
|
check_name=f"LOI Status ({partner.name})",
|
|
status=status,
|
|
expected=expected,
|
|
actual=actual,
|
|
details=details
|
|
))
|
|
|
|
return results
|
|
|
|
def run_all_checks(self) -> bool:
|
|
"""Run all validation checks and store results."""
|
|
print("=" * 80)
|
|
print("PQC INTEGRATION BUDGET & PERSON-MONTH VALIDATION")
|
|
print("=" * 80)
|
|
print()
|
|
|
|
# Calculate totals
|
|
total_budget, total_pm = self.calculate_totals()
|
|
|
|
# Run checks
|
|
self.results.append(self.check_total_budget(total_budget))
|
|
self.results.append(self.check_total_person_months(total_pm))
|
|
self.results.extend(self.check_budget_distribution())
|
|
self.results.extend(self.check_loi_status())
|
|
|
|
# Check if all passed
|
|
all_passed = all(r.status == CheckStatus.PASS for r in self.results)
|
|
has_warnings = any(r.status == CheckStatus.WARN for r in self.results)
|
|
has_failures = any(r.status == CheckStatus.FAIL for r in self.results)
|
|
|
|
return all_passed, has_warnings, has_failures
|
|
|
|
def print_partner_breakdown(self):
|
|
"""Print detailed partner breakdown table."""
|
|
print("\n" + "=" * 80)
|
|
print("PARTNER BREAKDOWN")
|
|
print("=" * 80)
|
|
print()
|
|
print(f"{'Partner':<35} {'Country':<8} {'Budget':<15} {'%':<8} {'PM':<8} {'FTE':<6}")
|
|
print("-" * 80)
|
|
|
|
for partner in self.partners:
|
|
budget_str = f"€{partner.budget_eur:,}"
|
|
pct_str = f"{partner.budget_pct:.1f}%"
|
|
pm_str = f"{partner.person_months:.1f}"
|
|
fte_str = f"{partner.pm_fte_avg:.2f}"
|
|
|
|
print(f"{partner.name:<35} {partner.country:<8} {budget_str:<15} {pct_str:<8} {pm_str:<8} {fte_str:<6}")
|
|
|
|
# Print totals
|
|
total_budget, total_pm = self.calculate_totals()
|
|
total_fte = total_pm / self.PROJECT_MONTHS
|
|
print("-" * 80)
|
|
print(f"{'TOTAL':<35} {'':<8} {'€{:,}'.format(total_budget):<15} {'100.0%':<8} {f'{total_pm:.1f}':<8} {f'{total_fte:.2f}':<6}")
|
|
print()
|
|
|
|
def print_validation_results(self):
|
|
"""Print validation results in formatted table."""
|
|
print("\n" + "=" * 80)
|
|
print("VALIDATION RESULTS")
|
|
print("=" * 80)
|
|
print()
|
|
print(f"{'Check':<40} {'Status':<10} {'Expected':<20} {'Actual':<20}")
|
|
print("-" * 80)
|
|
|
|
for result in self.results:
|
|
status_symbol = result.status.value
|
|
print(f"{result.check_name:<40} {status_symbol:<10} {result.expected:<20} {result.actual:<20}")
|
|
if result.details:
|
|
print(f" → {result.details}")
|
|
|
|
print()
|
|
|
|
def print_summary(self, all_passed: bool, has_warnings: bool, has_failures: bool):
|
|
"""Print final summary with recommendations."""
|
|
print("=" * 80)
|
|
print("SUMMARY")
|
|
print("=" * 80)
|
|
print()
|
|
|
|
total_checks = len(self.results)
|
|
passed = sum(1 for r in self.results if r.status == CheckStatus.PASS)
|
|
warned = sum(1 for r in self.results if r.status == CheckStatus.WARN)
|
|
failed = sum(1 for r in self.results if r.status == CheckStatus.FAIL)
|
|
|
|
print(f"Total Checks: {total_checks}")
|
|
print(f"✓ Passed: {passed}")
|
|
print(f"⚠ Warnings: {warned}")
|
|
print(f"✗ Failed: {failed}")
|
|
print()
|
|
|
|
if all_passed:
|
|
print("🎉 ALL CHECKS PASSED — Budget ready for submission!")
|
|
print()
|
|
print("Next steps:")
|
|
print(" 1. Verify all partner PICs are registered on EU Funding & Tenders Portal")
|
|
print(" 2. Ensure consortium agreement includes these budget allocations")
|
|
print(" 3. Cross-check with Part B Section 3.1 (Work Plan & Resources)")
|
|
print(" 4. Run this checker again if any changes are made to consortium-tracker.csv")
|
|
return True
|
|
elif has_failures:
|
|
print("❌ CRITICAL ISSUES DETECTED — Budget requires fixes before submission!")
|
|
print()
|
|
print("Action required:")
|
|
print(" 1. Review failed checks above")
|
|
print(" 2. Update consortium-tracker.csv with corrected values")
|
|
print(" 3. Re-run budget_checker.py to verify fixes")
|
|
print(" 4. Notify steering committee if budget reallocation needed (requires 75% vote)")
|
|
return False
|
|
elif has_warnings:
|
|
print("⚠️ WARNINGS DETECTED — Budget mostly ready, minor issues to address")
|
|
print()
|
|
print("Recommended actions:")
|
|
print(" 1. Review warnings above (may be acceptable variances)")
|
|
print(" 2. Confirm with steering committee if warnings are acceptable")
|
|
print(" 3. Document any intentional deviations in consortium agreement")
|
|
print(" 4. Re-run checker after any corrections")
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
# Determine path to consortium-tracker.csv (relative to this script)
|
|
script_dir = Path(__file__).parent
|
|
csv_path = script_dir.parent / "consortium" / "consortium-tracker.csv"
|
|
|
|
print(f"PQC Integration Budget Checker v1.0")
|
|
print(f"Checking: {csv_path}")
|
|
print()
|
|
|
|
checker = BudgetChecker(csv_path)
|
|
|
|
# Load CSV
|
|
if not checker.load_csv():
|
|
sys.exit(1)
|
|
|
|
# Print partner breakdown
|
|
checker.print_partner_breakdown()
|
|
|
|
# Run validation checks
|
|
all_passed, has_warnings, has_failures = checker.run_all_checks()
|
|
|
|
# Print results
|
|
checker.print_validation_results()
|
|
checker.print_summary(all_passed, has_warnings, has_failures)
|
|
|
|
# Exit with appropriate code
|
|
if has_failures:
|
|
sys.exit(2) # Critical failures
|
|
elif has_warnings:
|
|
sys.exit(1) # Warnings only
|
|
else:
|
|
sys.exit(0) # All passed
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|