Przejdź do treści

03 - Data Flow (jak wszystko się rusza)

Dwa główne flowy: CAPTURE PIPELINE (screenshot/text/audio → classify → route) i VOICE CONVERSATION (Konrad gada z Kony).


Flow 1: Capture Pipeline

KONRAD
  │  (Flutter apka: tap orb=Conv, tap +=action_sheet=capture,
  │   albo share intent z Chrome/Photos/WhatsApp)
Flutter POST capture.chcedointernetu.pl/api/capture
  │  FormData: {screenshot|audio|text, title, url, source_app, timestamp_iso}
  │  Header: X-Capture-Secret
  │  (CF edge sprawdza secret, jesli OK -> ctx.waitUntil(processCapture))
  │  Response ~50ms: {status: "processing", capture_id}
CF Worker: processCapture (background)
  ├─ 1. transcribeAudio (jesli audio) -> voice.chcedointernetu.pl/whisper
  │   (WAŻNE: jesli KonyAI voice conversation, uzywamy /converse w brain,
  │    ale tu w /api/capture uzywamy prostszego whisper transcribe)
  ├─ 2. enrichTextWithUrlPreview (jesli URL w text)
  │   -> voice.chcedointernetu.pl/enrich/text (kony-brain /enrich/text)
  ├─ 3. uploadToDrive (screenshot lub audio) - opcjonalne
  │   -> Google Drive API z OAuth refresh token
  │   Wynik: drive_url
  ├─ 4. classifyIntent (Vision LLM)
  │   Provider: env.VISION_PROVIDER
  │     - "qwen" (default): POST qwen-vl.chcedointernetu.pl/v1/chat/completions
  │        (Qwen2.5-VL-7B-Instruct-AWQ, multimodal)
  │     - "claude" (fallback): POST api.anthropic.com/v1/messages
  │        (Sonnet 4.6, multimodal, dla failoveru)
  │   Prompt: "Jestes Kony, sklasyfikuj capture na intent + short_title + analysis
  │            + wypelnij invoice_fields/task_fields/contact_fields per intent"
  │   Wynik: {intent, confidence, short_title, analysis, suggestion, *_fields}
  ├─ 5. commitToGithub (markdown snapshot capture do konrad-brain repo)
  │   Format: second-brain/konrad-brain/INBOX/z-komorki/YYYY-MM-DD-{title}.md
  │   Zawiera: original text/image link, klasyfikacje, links do CRM
  │   Wynik: github_md_raw_url (przydaje sie do Kony Hub referencji)
  ├─ 6. Router dispatch (per intent):
  │   ├─ FAKTURA -> routeToKSeF (jesli PL) albo routeToFlexyCRM (jesli zagraniczna)
  │   ├─ TASK -> routeToFlexyCRM (project match przez task_fields.client_hint + project_hint)
  │   ├─ KONTAKT -> routeToFlexyCRM (create contact person)
  │   ├─ IDEA/BUG/NOTATKA -> tylko Kony Hub (bez FlexyCRM)
  ├─ 7. pushToKonyHub (ZAWSZE - kazdy capture ma trafic do Hub)
  │   POST crm.chcedointernetu.pl/api/kony/captures
  │   Body: {capture_id, captured_at, source_app, intent, confidence,
  │          short_title, original_text, agent_suggestion, llm_analysis,
  │          drive_url, github_md_raw_url, ksef_id, flexy_task_id, ...}
  │   Wynik: {id: N, created: true} -> Kony #N
  └─ 8. ntfy push notification -> ntfy.chcedointernetu.pl/kony
      Message: "Kony #N: {short_title} -> {intent}"
      URL: link do Kony Hub UI

Latency total (typowe): 3-8s (Vision LLM to najwolniejsze - Qwen VL ~3-5s, Claude Sonnet ~2-4s).

Konrad widzi: instant "Wyslane!" screen w apce (bo Worker zwraca 200 od razu), ~30-60s później push notification na phone gdy Worker skonczy processCapture.


Flow 2: Voice Conversation

KONRAD (Flutter, Home → tap aureola → ConversationScreen)
  │  Naciśnij orb (Tap mode) albo przytrzymaj (Hold mode)
  │  → nagrywa audio (mic AAC 16kHz mono)
Flutter POST voice.chcedointernetu.pl/converse
  │  FormData: {audio: WAV/AAC, history: [prev turns]}
kony-brain :8000 (przez tunel)
  ├─ 1. Whisper STT
  │   POST localhost:8010/v1/audio/transcriptions
  │   Wynik: user_text (transkrypcja PL)
  ├─ 2. RAG search (auto-inject)
  │   POST localhost:8018/embed - embed user_text
  │   Cosine similarity vs in-memory index (kony-brain trzyma embeddings
  │   wszystkich captures z Kony Hub, backfill przy startup)
  │   Wynik: top-3 relevant captures z similarity > 0.72
  ├─ 3. Qwen 32B orchestrator (z tools + RAG context)
  │   POST localhost:8001/v1/chat/completions
  │   messages = [
  │     {role: 'system', content: QWEN_SYSTEM_PROMPT + RAG_CONTEXT},
  │     ...history,
  │     {role: 'user', content: user_text}
  │   ]
  │   tools = [27 tools registry z tools.py]
  │   Qwen decyduje:
  │     A) reply direct (text) - odpowiedz bez tool call
  │     B) tool_call - wywoła jeden z 27 tools:
  │        - search_captures_semantic (RAG dla dodatkowego kontekstu)
  │        - google_calendar_today/this_week/next_event (przez n8n Kony Bridge)
  │        - gmail_recent/search
  │        - homeassistant_turn_on/off/speak
  │        - flexycrm_create_task/find_project/etc.
  │        - save_conversation_capture (zapisz turn do Kony Hub)
  │   Jesli tool_call: kony-brain wykonuje przez bridge_tools.py (webhook do n8n)
  │   albo bezposrednio (FlexyCRM REST, RAG search)
  │   Wynik tool -> back to Qwen jako function_result, Qwen finalizuje reply
  ├─ 4. Bielik-guard safety check (opcjonalny)
  │   POST localhost:8002/classify - reply_text
  │   Jesli safety_score < 0.7 -> zablokuj, wyślij fallback message
  ├─ 5. Bielik-11B polish (dla naturalnego PL brzmienia)
  │   POST localhost:8000/v1/chat/completions
  │   Prompt: "Przeformułuj po polsku naturalnie, nieformalnie:"
  │   Wynik: polish_reply_text
  ├─ 6. XTTS voice clone
  │   POST localhost:8017/tts
  │   Body: {text: polish_reply_text, voice_reference: konrad.wav}
  │   Wynik: WAV bytes (glos Konrada)
  └─ 7. Response
      {
        user_text: "...",           # transcript
        reply_text: "...",          # tekst pokazany w apce
        audio_bytes: base64 WAV,    # audio do odtworzenia
        tool_calls: [...],          # co Qwen zrobił (dla debug)
        rag_context: [...],         # jakie captures Qwen widzial
      }

Flutter otrzymuje response, gra audio (just_audio), wyswietla transcript w chat bubbles.

Latency total: 1-3s dla prostego reply (bez tool call), 3-8s dla tool_call flow (dodatkowy round-trip do n8n / FlexyCRM).


Flow 3: Widget "Rozmowa z Konym" (bypass Home)

Home screen Android → tap widget "Rozmowa" (kony_widget_conversation)
Android WidgetProvider -> Intent z action='conversation' -> MainActivity
Flutter main() sprawdza getInitialAction PRZED runApp
  │  Jesli initialAction=='conversation':
KonyApp build z routes: {'/': Home, '/conversation': Conv}, initialRoute='/conversation'
  │  Flutter tworzy stack: [Home, Conversation]
  │  User widzi natychmiast Conversation, back button -> Home
ConversationScreen loaded, mic permission, gotowy do rozmowy

Dodane 2026-09-01: initial route bypass Home flash. Wcześniej Home się renderował 300ms przed push Conversation.


Flow 4: SALDEO Faktury (autonomiczne, cron)

n8n cron @HH:00:50 (co godzine) - workflow Aa71Vkjx49JPVX0e
  ├─ 1. Gmail search
  │   Query: 'has:attachment (subject:invoice OR faktura OR receipt OR rechnung OR fattura)
  │           newer_than:5d -label:SALDEO-* -from:allegro.pl -from:leaselink.pl
  │           -from:morele.net -from:tygrys.net
  │           -from:ratunku@... -to:viking_potatoes@... -in:sent'
  │   (Ostatnie exclude broni przed feedback loop)
  ├─ 2. Loop po mailach - Split In Batches
  ├─ 3. Gmail get message + attachments (osobny node bo getAll nie downloaduje attachmentow)
  ├─ 4. Extract text (JS Code) - build qwen_input z subject + sender + body + attachments
  ├─ 5. Qwen 32B classify invoice
  │   POST 10.0.0.114:8011/v1/chat/completions (uwaga: NIE :8001, dedykowany port n8n->Qwen)
  │   System prompt: "Jestes ekstraktorem danych faktur zagranicznych..."
  │   Wynik JSON: {is_invoice, confidence, invoice_date, amount, currency,
  │                supplier_name, is_foreign, is_ksef_covered}
  ├─ 6. BDH shadow classify (dodane 2026-09-01, task #94)
  │   POST bdh.chcedointernetu.pl/classify
  │   Wynik: {predicted_intent: FAKTURA|OTHER, confidence, probs}
  │   (Nie zmienia decyzji, tylko logujemy do Kony Hub llm_analysis)
  ├─ 7. Doklej response do meta - Set node
  │   choices (Qwen), bdh_predicted, bdh_confidence, __gmail_msg_id, __subject, etc.
  ├─ 8. Rule engine (JS Code) - decyzja:
  │   - qwen_parsed.is_invoice + is_foreign + !is_ksef_covered → SEND
  │   - qwen_parsed.confidence < 0.7 → MANUAL (label review)
  │   - reszta → SKIP (label skipped)
  ├─ 9. Switch po decyzji
  │   ├─ SEND branch:
  │   │   ├─ Gmail send do viking_potatoes@dok.saldeo.pl z załącznikami (attachmentsBinary)
  │   │   ├─ Gmail label SALDEO-sent
  │   │   └─ HTTP push Kony Hub (log capture z BDH info w llm_analysis)
  │   ├─ MANUAL: Gmail label 'SALDEO-review'
  │   └─ SKIP: Gmail label 'SALDEO-skipped'
  └─ (loop next mail)

Obecny stan (2026-09-01): source_app=faktury-saldeo-dryrun (DRY-RUN mode, log do Hub, nie wysyla realnie do SALDEO - test przed prod push).


Flow 5: DMARC Watchdog (autonomiczne, cron)

n8n cron @HH:*:15 (co 15 min) - workflow rTasxDFwvfzN7p8h
  ├─ 1. Gmail search: nowe DMARC reports w spam-reports@ (alias ratunku)
  ├─ 2. Extract XML z gzip attachment
  ├─ 3. Parse XML → domena + auth results
  ├─ 4. Rule engine:
  │     - Jesli wszystkie DKIM/SPF pass → archiwizuj + label DMARC-OK
  │     - Jesli failure → alert email + label DMARC-ALERT
  └─ (loop)

Pokrewne