Promote develop → main (2026-07-25 23:31 UTC) #53
@@ -15,9 +15,12 @@ einheitlich in Monitor und Verwaltungsportal).
|
||||
|
||||
## Was eine Aktion kostet
|
||||
|
||||
Im Modus `flat` (Voreinstellung) kostet jede Aktion einen festen Satz aus
|
||||
`CREDIT_TARIFF` in `src/config.py`, unabhängig davon, was sie uns tatsächlich
|
||||
verursacht hat. 1 Credit entspricht 0,20 USD.
|
||||
Im Modus `flat` (Voreinstellung) kostet jede Aktion einen festen Satz,
|
||||
unabhängig davon, was sie uns tatsächlich verursacht hat. 1 Credit entspricht
|
||||
0,20 USD. Die wirksamen Sätze stehen seit 25.07.2026 in der geteilten Tabelle
|
||||
`billing_tariff` und sind über den Verbrauchsrechner des Verwaltungsportals
|
||||
pflegbar. `CREDIT_TARIFF` in `src/config.py` befüllt die Tabelle beim ersten
|
||||
Start und bleibt Rückfallebene für fehlende Schlüssel.
|
||||
|
||||
| Aktion | Credits | Herkunft des Werts |
|
||||
|---|---|---|
|
||||
@@ -138,8 +141,6 @@ Zur Orientierung bei einem Kontingent von 10.000 Credits und 45 Credits je Lauf.
|
||||
## Offene Punkte
|
||||
|
||||
- E-Mail-Versand der Budget-Warnung.
|
||||
- Eingabefelder für die Stellwerte im Verwaltungsportal (heute erzeugt das
|
||||
Anlegen einer Organisation dort nicht einmal eine Lizenz).
|
||||
- Takt-Untergrenze und Kostenvorschau aus dem Lokal-Fork portieren (siehe oben).
|
||||
- Nachkaufpakete. Ohne sie ist der Deckel eine Sackgasse, mit ihnen eine
|
||||
Umsatzquelle. Der Verkauf gehört ins Verwaltungsportal, im Monitor müsste nur
|
||||
|
||||
@@ -3,7 +3,7 @@ import aiosqlite
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from config import DB_PATH, DATA_DIR
|
||||
from config import DB_PATH, DATA_DIR, CREDIT_TARIFF
|
||||
|
||||
logger = logging.getLogger("osint.database")
|
||||
|
||||
@@ -896,6 +896,30 @@ async def init_db():
|
||||
await db.commit()
|
||||
logger.info("Migration: unlimited_budget zu Lizenzen hinzugefuegt")
|
||||
|
||||
# Migration: Preistabelle fuer die Credits-Saetze. Pflegbare Quelle der
|
||||
# Wahrheit fuer Monitor UND Verwaltungsportal (das Portal schreibt sie
|
||||
# ueber den Verbrauchsrechner). Beim ersten Start mit den wirksamen
|
||||
# Konfigurationswerten befuellt, danach gewinnt die Tabelle,
|
||||
# CREDIT_TARIFF bleibt Rueckfallebene fuer fehlende Schluessel.
|
||||
cursor = await db.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='billing_tariff'"
|
||||
)
|
||||
if not await cursor.fetchone():
|
||||
await db.execute("""
|
||||
CREATE TABLE billing_tariff (
|
||||
tariff_key TEXT PRIMARY KEY,
|
||||
credits REAL NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
for tariff_key, credits in CREDIT_TARIFF.items():
|
||||
await db.execute(
|
||||
"INSERT INTO billing_tariff (tariff_key, credits) VALUES (?, ?)",
|
||||
(tariff_key, float(credits)),
|
||||
)
|
||||
await db.commit()
|
||||
logger.info("Migration: billing_tariff angelegt und mit Konfigurationswerten befuellt")
|
||||
|
||||
# Migration: Token-Usage-Monatstabelle
|
||||
cursor = await db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='token_usage_monthly'")
|
||||
if not await cursor.fetchone():
|
||||
|
||||
@@ -371,6 +371,24 @@ async def _record_usage_statistics(
|
||||
)
|
||||
|
||||
|
||||
async def _load_tariff(db: aiosqlite.Connection) -> dict:
|
||||
"""Wirksame Credits-Saetze je Aktion.
|
||||
|
||||
Die Tabelle billing_tariff ist die pflegbare Quelle (das Verwaltungsportal
|
||||
schreibt sie), CREDIT_TARIFF aus der Konfiguration bleibt Rueckfallebene
|
||||
fuer fehlende Schluessel oder eine noch fehlende Tabelle.
|
||||
"""
|
||||
tariff = dict(CREDIT_TARIFF)
|
||||
try:
|
||||
cursor = await db.execute("SELECT tariff_key, credits FROM billing_tariff")
|
||||
for row in await cursor.fetchall():
|
||||
if row["credits"] is not None and row["credits"] > 0:
|
||||
tariff[row["tariff_key"]] = float(row["credits"])
|
||||
except Exception:
|
||||
pass # Tabelle existiert noch nicht, die Konfiguration gilt
|
||||
return tariff
|
||||
|
||||
|
||||
async def _charge_credits(
|
||||
db: aiosqlite.Connection,
|
||||
tenant_id: int,
|
||||
@@ -398,7 +416,8 @@ async def _charge_credits(
|
||||
key = _tariff_key(source, incident_type)
|
||||
|
||||
if BILLING_MODE == "flat":
|
||||
credits_consumed = CREDIT_TARIFF.get(key)
|
||||
tariff = await _load_tariff(db)
|
||||
credits_consumed = tariff.get(key)
|
||||
if credits_consumed is None:
|
||||
# Unbekannte Quelle. Lieber auf die echte Rechnung zurueckfallen als
|
||||
# stillschweigend gratis abzugeben.
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren