feat(control-pipeline): add SDK endpoint demo package for applicability tests

Request payloads + response contract + api_runner.py for 6 priority cases.
Can be run directly against /v1/applicability/evaluate endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-23 19:11:44 +02:00
parent ae5c5c24eb
commit a7c6ffe4dd
10 changed files with 311 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
# Applicability SDK Demo Contract Package
## Ziel
Diese Version ist dafür gedacht, die Demo-Cases direkt gegen euren echten Endpoint zu schießen.
## Struktur
- `requests/CASE-*.json` — Request-Payloads je Demo-Case
- `contracts/response_contract.json` — fachlicher Mindestvertrag
- `contracts/response_schema.json` — JSON-Schema für die technische Response-Struktur
- `api_runner.py` — POSTet alle Cases an euren Endpoint und speichert die Responses
- `../applicability_demo/evaluator.py` — kann anschließend gegen die gespeicherten Responses laufen
## Beispielablauf
### 1. Cases gegen euren Endpoint schicken
```bash
python api_runner.py --endpoint http://127.0.0.1:8098/v1/applicability/evaluate
```
Die Responses landen dann in:
```text
actual_outputs/CASE-001.json
...
```
### 2. Gegen den Evaluator prüfen
```bash
python ../applicability_demo/evaluator.py --cases ../applicability_demo/demo_cases.yaml --actual-dir ./actual_outputs --report-json ./reports/latest_report.json --report-md ./reports/latest_report.md
```
## Erwartung an euren Endpoint
Request:
- JSON POST
- Request Body entspricht den Dateien in `requests/`
Response:
- Muss mindestens die Felder aus `contracts/response_contract.json` enthalten
## Hinweise
- Wenn euer Endpoint andere Feldnamen nutzt, baut einen kleinen Adapter vor dem Evaluator.
- Wenn ihr mehrere Modi habt, könnt ihr `mode` nutzen, um deterministische Applicability-Analysen zu erzwingen.
- Für Grenzfälle wie `CASE-011` soll das System nicht künstlich sicher tun, sondern eskalieren.

View File

@@ -0,0 +1,56 @@
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from urllib import request, error
def post_json(url: str, payload: dict, timeout: int = 60) -> dict:
data = json.dumps(payload).encode("utf-8")
req = request.Request(
url,
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
with request.urlopen(req, timeout=timeout) as resp:
raw = resp.read().decode("utf-8")
return json.loads(raw)
def main() -> None:
parser = argparse.ArgumentParser(description="Send demo applicability cases to an API endpoint.")
parser.add_argument("--endpoint", required=True, help="Full HTTP endpoint URL")
parser.add_argument("--requests-dir", type=Path, default=Path(__file__).resolve().parent / "requests")
parser.add_argument("--out-dir", type=Path, default=Path(__file__).resolve().parent / "actual_outputs")
parser.add_argument("--case-id", default=None, help="Optional single case id, e.g. CASE-001")
args = parser.parse_args()
args.out_dir.mkdir(parents=True, exist_ok=True)
files = sorted(args.requests_dir.glob("CASE-*.json"))
if args.case_id:
files = [args.requests_dir / f"{args.case_id}.json"]
failures = 0
for path in files:
payload = json.loads(path.read_text(encoding="utf-8"))
try:
result = post_json(args.endpoint, payload)
except error.HTTPError as exc:
failures += 1
print(f"[FAIL] {path.name}: HTTP {exc.code}", file=sys.stderr)
continue
except Exception as exc:
failures += 1
print(f"[FAIL] {path.name}: {exc}", file=sys.stderr)
continue
out_path = args.out_dir / path.name
out_path.write_text(json.dumps(result, indent=2, ensure_ascii=False), encoding="utf-8")
print(f"[OK] wrote {out_path}")
raise SystemExit(1 if failures else 0)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,35 @@
{
"name": "ApplicabilityAssessmentResponse",
"description": "Mindestvertrag für Responses des Applicability-Endpoints.",
"required_fields": {
"case_id": "string",
"assigned_controls": [
"string"
],
"excluded_controls": [
"string"
],
"escalations": [
"string"
],
"inferred_industries": [
"string"
],
"confidence": {
"overall": "number",
"industry_assignment": "number",
"control_assignment": "number"
},
"explanation": "string",
"uncertainty_flags": [
"string"
]
},
"semantic_rules": [
"must_assign controls müssen in assigned_controls enthalten sein",
"must_not_assign controls dürfen nicht in assigned_controls enthalten sein",
"escalate_for_legal_review muss in escalations abgebildet werden",
"Grenzfälle sollen uncertainty_flags setzen",
"explanation muss die juristische oder fachliche Abgrenzung nachvollziehbar beschreiben"
]
}

View File

@@ -0,0 +1,73 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "ApplicabilityAssessmentResponse",
"type": "object",
"required": [
"case_id",
"assigned_controls",
"excluded_controls",
"escalations",
"inferred_industries",
"confidence",
"explanation",
"uncertainty_flags"
],
"properties": {
"case_id": {
"type": "string"
},
"assigned_controls": {
"type": "array",
"items": {
"type": "string"
}
},
"excluded_controls": {
"type": "array",
"items": {
"type": "string"
}
},
"escalations": {
"type": "array",
"items": {
"type": "string"
}
},
"inferred_industries": {
"type": "array",
"items": {
"type": "string"
}
},
"confidence": {
"type": "object",
"required": [
"overall",
"industry_assignment",
"control_assignment"
],
"properties": {
"overall": {
"type": "number"
},
"industry_assignment": {
"type": "number"
},
"control_assignment": {
"type": "number"
}
}
},
"explanation": {
"type": "string"
},
"uncertainty_flags": {
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": true
}

View File

@@ -0,0 +1,20 @@
{
"case_id": "CASE-001",
"mode": "applicability_assessment",
"jurisdiction": "DE",
"company_profile": {
"company_type": "GmbH",
"primary_industry": "retail_ecommerce",
"summary": "Ein deutsches Unternehmen betreibt einen Webshop für physische Produkte. Zahlungen werden über Stripe Checkout abgewickelt. Das Unternehmen hält selbst keine Kundengelder, führt keine Zahlungskonten und bietet keine eigenen Zahlungsdienste an."
},
"facts": {
"sells_physical_products": true,
"webshop": true,
"payment_provider": "Stripe",
"stores_card_data": false,
"holds_customer_funds": false,
"operates_payment_service": false,
"processes_personal_data": true,
"sends_data_to_stripe": true
}
}

View File

@@ -0,0 +1,18 @@
{
"case_id": "CASE-002",
"mode": "applicability_assessment",
"jurisdiction": "DE",
"company_profile": {
"company_type": "AG",
"primary_industry": "financial_services",
"summary": "Eine Bank gibt physische TAN-Generatoren mit eingebauter Batterie an Endkunden aus. Die Geräte werden unter eigener Marke vertrieben."
},
"facts": {
"provides_banking_services": true,
"distributes_physical_products": true,
"product_contains_battery": true,
"product_under_own_brand": true,
"imports_product_from_non_eu": false,
"manufactures_product": false
}
}

View File

@@ -0,0 +1,17 @@
{
"case_id": "CASE-004",
"mode": "applicability_assessment",
"jurisdiction": "DE",
"company_profile": {
"company_type": "GmbH",
"primary_industry": "financial_services",
"summary": "Ein Fintech bietet eine App mit Wallet-Funktion, Kundengelder werden entgegengenommen und an Händler weitergeleitet."
},
"facts": {
"provides_wallet": true,
"holds_customer_funds": true,
"executes_payment_transactions": true,
"customer_onboarding": true,
"transaction_monitoring": true
}
}

View File

@@ -0,0 +1,16 @@
{
"case_id": "CASE-006",
"mode": "applicability_assessment",
"jurisdiction": "DE",
"company_profile": {
"company_type": "UG",
"primary_industry": "software_saas",
"summary": "Eine SaaS-Plattform verschickt Login-Codes per Twilio/SMS-Gateway, betreibt aber kein eigenes öffentliches Telekommunikationsnetz und bietet keinen Telekommunikationsdienst am Markt an."
},
"facts": {
"sends_sms_notifications": true,
"uses_external_gateway": true,
"provides_public_telecom_services": false,
"operates_network": false
}
}

View File

@@ -0,0 +1,17 @@
{
"case_id": "CASE-008",
"mode": "applicability_assessment",
"jurisdiction": "DE",
"company_profile": {
"company_type": "GmbH",
"primary_industry": "software_saas",
"summary": "Ein Softwareunternehmen verkauft nun zusätzlich eigene IoT-Sensoren mit Batterie und Funkmodul unter eigener Marke."
},
"facts": {
"imports_from_non_eu": true,
"sells_hardware": true,
"product_contains_battery": true,
"product_has_radio": true,
"own_brand": true
}
}

View File

@@ -0,0 +1,17 @@
{
"case_id": "CASE-011",
"mode": "applicability_assessment",
"jurisdiction": "DE",
"company_profile": {
"company_type": "GmbH",
"primary_industry": "software_saas",
"summary": "Eine Plattform ermöglicht Händlern Auszahlungen, virtuelle Konten, Split Settlements und einen Finanzierungsvorschuss, teilweise über Partnerbanken, teilweise über eigene Prozesse."
},
"facts": {
"virtual_accounts": true,
"split_settlements": true,
"advance_payments": true,
"partner_bank_involved": true,
"own_funds_flow_unclear": true
}
}