Promote develop → main (2026-07-25 23:31 UTC) #53

Zusammengeführt
IntelSight_Admin hat 7 Commits von develop nach main 2026-07-26 01:31:20 +02:00 zusammengeführt
3 geänderte Dateien mit 15 neuen und 34 gelöschten Zeilen
Nur Änderungen aus Commit f8499c4e40 werden angezeigt - Alle Commits anzeigen

Datei anzeigen

@@ -61,11 +61,9 @@ Der Wechsel passiert träge bei der nächsten Lizenzprüfung, nicht über einen
Zeitplan. Ein verpasster Monatswechsel wird dadurch beim nächsten Zugriff Zeitplan. Ein verpasster Monatswechsel wird dadurch beim nächsten Zugriff
nachgeholt, und ohne Nutzung wird ohnehin nichts verbraucht. nachgeholt, und ohne Nutzung wird ohnehin nichts verbraucht.
`credits_rollover` überträgt ungenutzte Credits in den Folgemonat, gedeckelt Ungenutzte Credits verfallen zum Periodenende. Ein Übertrag in den Folgemonat
auf ein Monatskontingent. Der Übertrag steht in `credits_carried` und zählt beim war kurz vorgesehen und wurde als Produktentscheidung 07/2026 wieder entfernt,
Hard-Stop mit. Das ist empfehlenswert, weil OSINT-Nutzung ereignisgetrieben ist. es gilt der harte Monatsdeckel wie verkauft.
Bricht eine Krise aus, braucht der Kunde in einer Woche mehr als sonst im
Quartal, und genau dann darf das System nicht dichtmachen.
Eine Bestandslizenz ohne Periodenmarke bekommt beim ersten Zugriff die aktuelle Eine Bestandslizenz ohne Periodenmarke bekommt beim ersten Zugriff die aktuelle
Periode eingetragen, **ohne** den Verbrauch zurückzusetzen. Sonst würden ihr Periode eingetragen, **ohne** den Verbrauch zurückzusetzen. Sonst würden ihr
@@ -93,7 +91,6 @@ nächste Schritt.
| `credits_total` | Kontingent je Periode | 10000 | | `credits_total` | Kontingent je Periode | 10000 |
| `cost_per_credit` | nur für `BILLING_MODE=actual` und die interne Rechnung | 0.20 | | `cost_per_credit` | nur für `BILLING_MODE=actual` und die interne Rechnung | 0.20 |
| `credits_period` | `monthly` oder `total` | monthly | | `credits_period` | `monthly` oder `total` | monthly |
| `credits_rollover` | Übertrag erlauben | 1 |
| `budget_warning_percent` | Warnschwelle in Prozent | 80 | | `budget_warning_percent` | Warnschwelle in Prozent | 80 |
| `unlimited_budget` | Kontingent aushebeln | 0 | | `unlimited_budget` | Kontingent aushebeln | 0 |

Datei anzeigen

@@ -877,10 +877,9 @@ async def init_db():
# Beginn der laufenden Periode als YYYY-MM. Leer = beim naechsten # Beginn der laufenden Periode als YYYY-MM. Leer = beim naechsten
# Zugriff auf den aktuellen Monat gesetzt, ohne Verbrauch zu loeschen. # Zugriff auf den aktuellen Monat gesetzt, ohne Verbrauch zu loeschen.
await db.execute("ALTER TABLE licenses ADD COLUMN credits_period_start TEXT") await db.execute("ALTER TABLE licenses ADD COLUMN credits_period_start TEXT")
# Ungenutzte Credits in die Folgeperiode uebertragen. Aus = harter # Ungenutzte Credits verfallen zum Periodenende. Ein Uebertrag in den
# Monatsdeckel wie verkauft, An = faengt Krisenspitzen ab. # Folgemonat war kurz vorgesehen und wurde als Produktentscheidung
await db.execute("ALTER TABLE licenses ADD COLUMN credits_rollover INTEGER DEFAULT 0") # 07/2026 wieder entfernt (harter Monatsdeckel wie verkauft).
await db.execute("ALTER TABLE licenses ADD COLUMN credits_carried REAL DEFAULT 0")
# Verhindert, dass die Warnschwelle bei jeder Buchung erneut meldet. # Verhindert, dass die Warnschwelle bei jeder Buchung erneut meldet.
await db.execute("ALTER TABLE licenses ADD COLUMN budget_warning_sent INTEGER DEFAULT 0") await db.execute("ALTER TABLE licenses ADD COLUMN budget_warning_sent INTEGER DEFAULT 0")
await db.commit() await db.commit()

Datei anzeigen

@@ -41,8 +41,8 @@ async def roll_credit_period(db: aiosqlite.Connection, lic: dict) -> dict:
Das ist robuster, weil ein verpasster Monatswechsel beim naechsten Zugriff Das ist robuster, weil ein verpasster Monatswechsel beim naechsten Zugriff
ohnehin nachgeholt wird und ohne Nutzung auch nichts verbraucht wird. ohnehin nachgeholt wird und ohne Nutzung auch nichts verbraucht wird.
Bei aktivem Uebertrag wandert ungenutztes Guthaben in die Folgeperiode, Ungenutzte Credits verfallen zum Periodenende (kein Uebertrag,
gedeckelt auf ein Monatskontingent, damit es nicht unbegrenzt anwaechst. Produktentscheidung 07/2026).
Returns: Returns:
Das ggf. aktualisierte Lizenz-dict (in-place ergaenzt). Das ggf. aktualisierte Lizenz-dict (in-place ergaenzt).
@@ -70,33 +70,21 @@ async def roll_credit_period(db: aiosqlite.Connection, lic: dict) -> dict:
if started == period: if started == period:
return lic return lic
total = lic.get("credits_total") or 0
carried_old = lic.get("credits_carried") or 0
used = lic.get("credits_used") or 0
if lic.get("credits_rollover"):
carried_new = max(0.0, (total + carried_old) - used)
carried_new = min(carried_new, float(total)) # hoechstens ein Monat
else:
carried_new = 0.0
await db.execute( await db.execute(
"""UPDATE licenses """UPDATE licenses
SET credits_used = 0, credits_carried = ?, credits_period_start = ?, SET credits_used = 0, credits_period_start = ?,
budget_warning_sent = 0 budget_warning_sent = 0
WHERE id = ?""", WHERE id = ?""",
(round(carried_new, 2), period, lic["id"]), (period, lic["id"]),
) )
await db.commit() await db.commit()
lic["credits_used"] = 0 lic["credits_used"] = 0
lic["credits_carried"] = carried_new
lic["credits_period_start"] = period lic["credits_period_start"] = period
lic["budget_warning_sent"] = 0 lic["budget_warning_sent"] = 0
logger.info( logger.info(
f"Lizenz {lic['id']}: neue Periode {period}, Verbrauch zurueckgesetzt " f"Lizenz {lic['id']}: neue Periode {period}, Verbrauch zurueckgesetzt"
f"(Uebertrag {round(carried_new, 2)} Einheiten)"
) )
return lic return lic
@@ -185,10 +173,10 @@ async def check_license(db: aiosqlite.Connection, organization_id: int) -> dict:
unlimited_budget = bool(lic_dict.get("unlimited_budget")) unlimited_budget = bool(lic_dict.get("unlimited_budget"))
credits_total = lic_dict.get("credits_total") credits_total = lic_dict.get("credits_total")
credits_used = lic_dict.get("credits_used") or 0 credits_used = lic_dict.get("credits_used") or 0
credits_carried = lic_dict.get("credits_carried") or 0
credits_period = lic_dict.get("credits_period") or "monthly" credits_period = lic_dict.get("credits_period") or "monthly"
# Verfuegbar ist das Kontingent plus ein etwaiger Uebertrag aus dem Vormonat. # Verfuegbar ist genau das Kontingent. Ungenutzte Credits verfallen zum
credits_available = (credits_total or 0) + credits_carried # Periodenende (kein Uebertrag, Produktentscheidung 07/2026).
credits_available = credits_total or 0
# STAGING_MODE: kein Token-Budget-Hard-Stop, immer unlimited # STAGING_MODE: kein Token-Budget-Hard-Stop, immer unlimited
if _staging_mode(): if _staging_mode():
@@ -219,7 +207,6 @@ async def check_license(db: aiosqlite.Connection, organization_id: int) -> dict:
pass pass
# Budget-Check (Hard-Stop bei aufgebrauchtem Guthaben, ausser unlimited). # Budget-Check (Hard-Stop bei aufgebrauchtem Guthaben, ausser unlimited).
# Geprueft wird gegen das verfuegbare Guthaben, also inklusive Uebertrag.
budget_exceeded = False budget_exceeded = False
if not unlimited_budget and credits_available > 0: if not unlimited_budget and credits_available > 0:
if credits_used >= credits_available: if credits_used >= credits_available:
@@ -245,7 +232,6 @@ async def check_license(db: aiosqlite.Connection, organization_id: int) -> dict:
"unlimited_budget": False, "unlimited_budget": False,
"credits_total": credits_total, "credits_total": credits_total,
"credits_used": credits_used, "credits_used": credits_used,
"credits_carried": credits_carried,
"credits_available": credits_available, "credits_available": credits_available,
"credits_period": credits_period, "credits_period": credits_period,
} }
@@ -262,7 +248,6 @@ async def check_license(db: aiosqlite.Connection, organization_id: int) -> dict:
"unlimited_budget": unlimited_budget, "unlimited_budget": unlimited_budget,
"credits_total": credits_total, "credits_total": credits_total,
"credits_used": credits_used, "credits_used": credits_used,
"credits_carried": credits_carried,
"credits_available": credits_available, "credits_available": credits_available,
"credits_period": credits_period, "credits_period": credits_period,
} }
@@ -437,7 +422,7 @@ async def _charge_credits(
) )
used_new = (lic.get("credits_used") or 0) + credits_consumed used_new = (lic.get("credits_used") or 0) + credits_consumed
available = (lic.get("credits_total") or 0) + (lic.get("credits_carried") or 0) available = lic.get("credits_total") or 0
logger.info( logger.info(
f"charge_usage_to_tenant[{key}] Tenant {tenant_id}: " f"charge_usage_to_tenant[{key}] Tenant {tenant_id}: "