128 lines
4.9 KiB
Python
128 lines
4.9 KiB
Python
"""
|
||
Akte Sync - Cron Poller
|
||
|
||
Polls the Advoware Watcher Redis Sorted Set every 10 seconds (10 s debounce):
|
||
|
||
advoware:pending_aktennummern – written by Windows Advoware Watcher
|
||
{ aktennummer → timestamp }
|
||
|
||
Eligibility (either flag triggers sync):
|
||
syncSchalter AND aktivierungsstatus in valid list → Advoware sync
|
||
aiAktivierungsstatus in valid list → xAI sync
|
||
|
||
EspoCRM webhooks emit akte.sync directly (no queue needed).
|
||
Failed akte.sync events are retried by Motia automatically.
|
||
"""
|
||
|
||
from motia import FlowContext, cron
|
||
|
||
|
||
config = {
|
||
"name": "Akte Sync - Cron Poller",
|
||
"description": "Poll Redis for pending Aktennummern and emit akte.sync events (10 s debounce)",
|
||
"flows": ["akte-sync"],
|
||
"triggers": [cron("*/10 * * * * *")],
|
||
"enqueues": ["akte.sync"],
|
||
}
|
||
|
||
# Queue 1: written by Windows Advoware Watcher (keyed by Aktennummer)
|
||
PENDING_ADVO_KEY = "advoware:pending_aktennummern"
|
||
PROCESSING_ADVO_KEY = "advoware:processing_aktennummern"
|
||
|
||
DEBOUNCE_SECS = 10
|
||
BATCH_SIZE = 5 # max items to process per cron tick
|
||
|
||
VALID_ADVOWARE_STATUSES = frozenset({'import', 'new', 'active'})
|
||
VALID_AI_STATUSES = frozenset({'new', 'active'})
|
||
|
||
|
||
async def handler(input_data: None, ctx: FlowContext) -> None:
|
||
import time
|
||
from services.redis_client import get_redis_client
|
||
from services.espocrm import EspoCRMAPI
|
||
|
||
ctx.logger.info("=" * 60)
|
||
ctx.logger.info("⏰ AKTE CRON POLLER")
|
||
|
||
redis_client = get_redis_client(strict=False)
|
||
if not redis_client:
|
||
ctx.logger.error("❌ Redis unavailable")
|
||
ctx.logger.info("=" * 60)
|
||
return
|
||
|
||
espocrm = EspoCRMAPI(ctx)
|
||
cutoff = time.time() - DEBOUNCE_SECS
|
||
|
||
advo_pending = redis_client.zcard(PENDING_ADVO_KEY)
|
||
ctx.logger.info(f" Pending (aktennr) : {advo_pending}")
|
||
|
||
processed_count = 0
|
||
|
||
# ── Queue: Advoware Watcher (by Aktennummer) ───────────────────────
|
||
advo_entries = redis_client.zrangebyscore(PENDING_ADVO_KEY, min=0, max=cutoff, start=0, num=BATCH_SIZE)
|
||
for raw in advo_entries:
|
||
aktennr = raw.decode() if isinstance(raw, bytes) else raw
|
||
score = redis_client.zscore(PENDING_ADVO_KEY, aktennr) or 0
|
||
age = time.time() - score
|
||
redis_client.zrem(PENDING_ADVO_KEY, aktennr)
|
||
redis_client.sadd(PROCESSING_ADVO_KEY, aktennr)
|
||
processed_count += 1
|
||
ctx.logger.info(f"📋 Aktennummer: {aktennr} (age={age:.1f}s)")
|
||
try:
|
||
result = await espocrm.list_entities(
|
||
'CAkten',
|
||
where=[{'type': 'equals', 'attribute': 'aktennummer', 'value': int(aktennr)}],
|
||
max_size=1,
|
||
)
|
||
if not result or not result.get('list'):
|
||
ctx.logger.warn(f"⚠️ No CAkten found for aktennummer={aktennr} – removing")
|
||
else:
|
||
akte = result['list'][0]
|
||
await _emit_if_eligible(akte, aktennr, ctx)
|
||
except Exception as e:
|
||
ctx.logger.error(f"❌ Error (aktennr queue) {aktennr}: {e}")
|
||
redis_client.zadd(PENDING_ADVO_KEY, {aktennr: time.time()})
|
||
finally:
|
||
redis_client.srem(PROCESSING_ADVO_KEY, aktennr)
|
||
|
||
if not processed_count:
|
||
if advo_pending > 0:
|
||
ctx.logger.info(f"⏸️ Entries pending but all too recent (< {DEBOUNCE_SECS}s)")
|
||
else:
|
||
ctx.logger.info("✓ Queue empty")
|
||
else:
|
||
ctx.logger.info(f"✓ Processed {processed_count} item(s)")
|
||
|
||
ctx.logger.info("=" * 60)
|
||
|
||
|
||
async def _emit_if_eligible(akte: dict, aktennr, ctx: FlowContext) -> None:
|
||
"""Check eligibility and emit akte.sync if applicable."""
|
||
akte_id = akte['id']
|
||
# Prefer aktennr from argument; fall back to entity field
|
||
aktennummer = aktennr or akte.get('aktennummer')
|
||
sync_schalter = akte.get('syncSchalter', False)
|
||
aktivierungsstatus = str(akte.get('aktivierungsstatus') or '').lower()
|
||
ai_status = str(akte.get('aiAktivierungsstatus') or '').lower()
|
||
|
||
advoware_eligible = bool(aktennummer) and sync_schalter and aktivierungsstatus in VALID_ADVOWARE_STATUSES
|
||
xai_eligible = ai_status in VALID_AI_STATUSES
|
||
|
||
ctx.logger.info(f" akte_id : {akte_id}")
|
||
ctx.logger.info(f" aktennummer : {aktennummer or '—'}")
|
||
ctx.logger.info(f" aktivierungsstatus : {aktivierungsstatus} ({'✅' if advoware_eligible else '⏭️'})")
|
||
ctx.logger.info(f" aiAktivierungsstatus : {ai_status} ({'✅' if xai_eligible else '⏭️'})")
|
||
|
||
if not advoware_eligible and not xai_eligible:
|
||
ctx.logger.warn(f"⚠️ Akte {akte_id} not eligible for any sync")
|
||
return
|
||
|
||
await ctx.enqueue({
|
||
'topic': 'akte.sync',
|
||
'data': {
|
||
'akte_id': akte_id,
|
||
'aktennummer': aktennummer, # may be None for xAI-only Akten
|
||
},
|
||
})
|
||
ctx.logger.info(f"📤 akte.sync emitted (akte_id={akte_id}, aktennummer={aktennummer or '—'})")
|