app:
  description: 経理の月次仕訳データから異常仕訳を検出し、リスクレベル付き確認依頼レポートを生成するワークフロー
  icon: 📘
  icon_background: '#FFEAD5'
  mode: workflow
  name: 月次仕訳異常検知レポート
  use_icon_as_answer_icon: false
kind: app
version: 0.6.0
workflow:
  conversation_variables: []
  environment_variables: []
  features:
    file_upload:
      image:
        enabled: false
    opening_statement: ''
    suggested_questions: []
    suggested_questions_after_answer:
      enabled: false
    text_to_speech:
      enabled: false
  graph:
    edges:
    - data:
        sourceType: start
        targetType: code
      id: start-to-code1
      source: start
      target: code1
    - data:
        sourceType: code
        targetType: llm
      id: code1-to-llm1
      source: code1
      target: llm1
    - data:
        sourceType: llm
        targetType: end
      id: llm1-to-end
      source: llm1
      target: end
    nodes:
    - data:
        title: 開始
        type: start
        variables:
        - label: query
          max_length: 2048
          options: []
          required: true
          type: paragraph
          variable: query
      height: 90
      id: start
      position:
        x: 80
        y: 282
      type: custom
      width: 244
    - data:
        title: '前処理: CSV解析と初期異常判定'
        type: code
        code_language: python3
        code: "def _safe_str(v):\n    \"\"\"Convert any value to string safely.\"\"\
          \"\n    if v is None:\n        return \"\"\n    if isinstance(v, list):\n\
          \        return \"\\n\".join(str(i) for i in v)\n    return str(v)\n\ndef\
          \ _safe_list(v):\n    \"\"\"Ensure value is a list.\"\"\"\n    if v is None:\n\
          \        return []\n    if isinstance(v, str):\n        return [v]\n   \
          \ if isinstance(v, list):\n        return v\n    return [v]\n\ndef main(**kwargs)\
          \ -> dict:\n    inputs = kwargs\n    # 設定値（閾値等はここで定義）\n    amount_threshold\
          \ = 1000000.0\n    results = []\n    text = inputs.get(\"query\", \"\")\
          \ or \"\"\n    # CSVヘッダ例: id,date,posting_date,amount,account,description,prev_month_amount\n\
          \    try:\n        import csv\n        import io\n        reader = csv.DictReader(io.StringIO(text))\n\
          \    except Exception:\n        return dict(output=dict(summary=dict(total=0,\
          \ flagged=0, high=0), items=[]))\n    from datetime import datetime\n  \
          \  for row in reader:\n        # 安全に値取得\n        raw_amount = row.get(\"\
          amount\", \"\") or \"0\"\n        try:\n            amount = float(raw_amount.replace(\"\
          ,\", \"\"))\n        except Exception:\n            amount = 0.0\n     \
          \   account = (row.get(\"account\", \"\") or \"\").strip()\n        description\
          \ = (row.get(\"description\", \"\") or \"\").strip()\n        date = (row.get(\"\
          date\", \"\") or \"\").strip()\n        posting = (row.get(\"posting_date\"\
          , \"\") or \"\").strip()\n        prev_raw = row.get(\"prev_month_amount\"\
          , \"\") or \"0\"\n        try:\n            prev_amount = float(prev_raw.replace(\"\
          ,\", \"\"))\n        except Exception:\n            prev_amount = 0.0\n\
          \        anomalies = []\n        # 金額超過\n        if amount > amount_threshold:\n\
          \            anomalies.append(\"amount_excess\")\n        # 科目不整合（ここでは科目欠落を検知）\n\
          \        if account == \"\":\n            anomalies.append(\"account_missing\"\
          )\n        # 日付逆転（posting_date < date の場合）\n        if posting:\n      \
          \      try:\n                d = datetime.fromisoformat(date)\n        \
          \        p = datetime.fromisoformat(posting)\n                if p < d:\n\
          \                    anomalies.append(\"date_reversed\")\n            except\
          \ Exception:\n                # フォーマット不明時はスキップ\n                pass\n \
          \       # 摘要欠落\n        if description == \"\":\n            anomalies.append(\"\
          description_missing\")\n        # 前月比急変（前月比が 50% 以上の変化）\n        try:\n\
          \            if abs(prev_amount) > 0.0:\n                change_ratio =\
          \ abs(amount - prev_amount) / (abs(prev_amount) + 1e-9)\n              \
          \  if change_ratio > 0.5:\n                    anomalies.append(\"prev_month_spike\"\
          )\n        except Exception:\n            pass\n        # リスク判定（単純ルール）\n\
          \        risk = \"low\"\n        if \"amount_excess\" in anomalies or \"\
          prev_month_spike\" in anomalies:\n            risk = \"high\"\n        elif\
          \ len(anomalies) > 0:\n            risk = \"medium\"\n        item = dict(\n\
          \            id = (row.get(\"id\", \"\") or \"\"),\n            date = date,\n\
          \            posting_date = posting,\n            amount = amount,\n   \
          \         account = account,\n            description = description,\n \
          \           anomalies = \",\".join(anomalies),\n            risk = risk\n\
          \        )\n        results.append(item)\n    total = len(results)\n   \
          \ flagged = sum(1 for r in results if r.get(\"anomalies\"))\n    high =\
          \ sum(1 for r in results if r.get(\"risk\") == \"high\")\n    summary =\
          \ dict(total=total, flagged=flagged, high=high)\n    return dict(output=dict(summary=summary,\
          \ items=results))\n"
        variables:
        - value_selector:
          - start
          - query
          variable: query
        outputs:
          output:
            type: object
            children: null
      height: 90
      id: code1
      position:
        x: 380
        y: 282
      type: custom
      width: 244
    - data:
        title: AI分析・レポート生成
        type: llm
        model:
          provider: openai
          name: gpt-5-mini
          mode: chat
          completion_params:
            temperature: 0.5
        prompt_template:
        - role: system
          text: 'あなたは経理の監査・コンプライアンス支援を行う専門アシスタントです。入力は前処理済みの仕訳データと初期検出結果です。出力は担当者が確認・対応できる「確認依頼レポート」をJSON形式で作成してください。出力JSONは必ず次の構造に従ってください。{
            "summary": {"total": <int>, "flagged": <int>, "high": <int>}, "items":
            [ {"id":"","date":"","amount":<number>,"account":"","anomalies":["..."],"risk":"low|medium|high","recommendation":"担当者向けの推奨対応（日本語）"}
            ] }。各items項目ごとに1つ以上の丁寧な対応理由と具体的な推奨対応を含めてください。


            ---


            【最終出力フォーマット (この指示が最優先)】

            最終出力は **読みやすい日本語の Markdown** で返してください。JSON

            配列・オブジェクトをそのまま出力してはいけません。

            - 見出し (## / ###) でセクションを区切る

            - リスト (- / 1.) や表 (| ... |) で構造化

            - 各項目の意図や根拠が分かるよう短い説明文を添える

            - 営業デモで顧客に直接見せられる体裁にする


            (構造化されたデータ項目を内部で扱うのは構いませんが、出力時に Markdown

            に変換してください。)'
        - role: user
          text: '前処理結果（構造化データ）:

            {{#code1.output#}}


            元の入力（参考）:

            {{#start.query#}}


            上記をもとに、月次仕訳の異常検知に関する担当者向け確認依頼レポートをJSONで出力してください。日本語で、各項目に短い説明と具体的な推奨対応（誰が何をすべきか）を含めてください。'
        vision:
          enabled: false
        memory: null
        context:
          enabled: false
      height: 90
      id: llm1
      position:
        x: 680
        y: 282
      type: custom
      width: 244
    - data:
        title: 終了
        type: end
        outputs:
        - value_selector:
          - llm1
          - text
          variable: result
      height: 90
      id: end
      position:
        x: 980
        y: 282
      type: custom
      width: 244
  hash: null
