# -*- coding: utf-8 -*-
import time

import requests
import json


def ai_get_brand_list(content, prompt):
    url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
    payload = json.dumps({
        "model": "doubao-seed-2-0-mini-260428",
        "messages": [
            {
                "role": "system",
                "content": """
                用户提供问题和结果提取信息，严格遵循以下核心逻辑与要求：
核心逻辑：优先识别文本核心表述对象 —— 若原文以品牌推荐为主，重点提取品牌名；若以厂名介绍为主，重点提取厂名；若以店名介绍为主，重点提取店名；若包含店名、公司名，均按文本核心导向同步提取（如门店推荐类文本优先提取店名，平台盘点类优先提取平台名）。
提取范围：仅提取商业主体名称（含品牌名、公司名、店名、厂名、品牌授权门店名、连锁门店名），彻底忽略地址、服务、优惠、功能描述等非名称类内容。
排序规则：严格遵循文本原文推荐的先后顺序（含分类、分点内的自然出现顺序，不打乱原文逻辑）。
去重规则：完全相同的名称仅保留 1 个；同一品牌 / 公司下的不同门店、分支机构（名称不同）视为不同主体，全部保留；名称中特殊字符（括号、空格等）不影响去重判断。
名称保留：完整保留名称原始表述，不做简化、缩写或修改（含括号、空格、特殊符号等）
输出格式：仅输出纯 JSON 数组，数组键名固定为不含任何解释、注释、标题等额外文字
                """
            },
            {
                "role": "user",
                "content": f"问题:{prompt}回答：{content}"
            }
        ],
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "brand_list",
                "strict": True,
                "schema": {
                    "type": "object",
                    "properties": {
                        "brands": {
                            "type": "array",
                            "items": {
                                "type": "string"
                            },
                            "description": "品牌列表，按原文出现顺序排列，每个元素是品牌名称字符串"
                        }
                    },
                    "required": [
                        "brands"
                    ],
                    "additionalProperties": False
                }
            }
        },
        "thinking": {
            "type": "disabled"
        },
        "temperature": 0
    })
    headers = {
        'Authorization': 'Bearer ark-7afc3be2-37a8-47fd-9f02-996258a3d305-27da0',
        'Content-Type': 'application/json'
    }

    response = requests.request("POST", url, headers=headers, data=payload)
    try:
        return json.loads(response.json().get("choices")[0].get("message").get("content")).get("brands")

    except Exception as e:
        return []

def ai_get_product_list(content, prompt):
    url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
    payload = json.dumps({
        "model": "doubao-seed-2-0-mini-260428",
        "messages": [
            {
                "role": "system",
                "content": """
你是一个产品词抽取器。

你会收到：
1. 监测问题
2. AI回答正文

任务：
从AI回答正文中，提取与监测问题直接相关的具体产品词。
在一次处理中同时完成产品识别、非产品过滤、去重和顺序整理。

【保留条件】
仅保留满足以下任一条件的词：
1. 能明确指向具体商品或具体产品；
2. 带型号、版本、代际、款型或明确规格的产品名；
3. 品牌名+具体产品名组合；
4. 能明确识别具体产品的别名或消费者常用名；
5. 在当前消费语境下可以被直接购买、推荐、比较或评价的具体对象。

【必须排除】
1. 纯品牌名、公司名、门店名；
2. 纯产品系列名，但没有具体产品、型号或款型；
3. 泛品类词，例如：面霜、手机、汽车、精华；
4. 成分、原料、技术、功效、需求、人群、肤质、场景词；
5. 价格、容量、时间、评价性描述；
6. 与监测问题无关、只是在正文中顺带出现的产品；
7. 无法确定是否属于具体产品的词。

【处理规则】
1. 必须保留正文中的原始写法，不得编造、补充或改写；
2. 按正文首次出现顺序输出；
3. 相同产品重复出现时只保留一次；
4. “品牌+产品名”和产品简称指向同一产品时，只保留信息更完整的一项；
5. 使用“/、逗号、顿号”等连接的多个独立产品，应分别提取；
6. 输出前自行检查每一项，确认不是纯品牌、纯系列或泛品类词；
7. 如果没有符合条件的产品，返回空数组。

仅输出以下JSON，不要输出解释：

{
  "product_words": []
}
                """
            },
            {
                "role": "user",
                "content":  f"""监测问题：{prompt} AI回答正文：{content}"""
            }
        ],
        "thinking": {
            "type": "disabled"
        },
        "temperature": 0
    })
    headers = {
        'Authorization': 'Bearer ark-7afc3be2-37a8-47fd-9f02-996258a3d305-27da0',
        'Content-Type': 'application/json'
    }

    response = requests.request("POST", url, headers=headers, data=payload)
    try:
        return json.loads(response.json().get("choices")[0].get("message").get("content")).get("product_words")

    except Exception as e:
        return []


def ai_get_product_relation_spu(product_list, keyword):
    url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
    payload = {
        "model": "doubao-seed-2-1-pro-260628",
        "messages": [
            {
                "role": "system",
                "content": """
你的核心任务为：基于给定的商品列表与查询关键词，为列表内的每一件商品匹配对应信息，最终输出符合规范要求的JSON结果，执行过程需严格遵循以下规则：
1. 需为商品列表中的每一件商品单独生成一条匹配记录，每条记录必须固定包含以下4个字段，各字段的取值规则明确如下：
   （1）produce_name：填写对应商品的完整原始名称，即商品列表中给出的该商品全称，不得做任何增删修改；
   （2）brand：填写该商品对应的SPU品牌名称，需精准识别商品所属品牌，参考示例：“北京同仁堂陈皮茯苓茶”的品牌取值为“北京同仁堂”，“红魔11 Pro+”的品牌取值为“红魔”；若商品无明确可识别的品牌信息，则直接返回该商品的完整名称作为brand字段值；
   （3）spu_name：填写该商品的标准SPU名称，即去除品牌前缀后的商品核心名称，参考示例：“北京同仁堂陈皮茯苓茶”的spu_name取值为“陈皮茯苓茶”，“一加 Ace 6 至尊版”的spu_name取值为“Ace 6 至尊版”；需注意同一款商品的spu_name表述必须完全统一，禁止出现同一SPU存在空格差异、格式差异的情况，例如不得同时出现“无线洗地机5”和“无线洗地机 5”这类仅空格/格式有区别的命名，需统一为规范表述；
   （4）current：判断该商品与给定查询关键词是否存在关联，关联判定范围包括但不限于：关键词为该商品的品牌名、关键词为该品牌旗下的子品牌/系列名称、商品属于该关键词对应的品牌产品线；只要满足上述任意一种关联情形，该字段取值为1，若不存在任何关联则取值为0。
2. 一致性校验特别要求：同批次传入的商品中，若商品标题指向的品牌名相同，brand字段的取值必须保持完全统一，禁止出现同一品牌同时标注“小米”和“xiaomi”这类中英文/不同写法混用的情况，需统一为规范名称；同批次商品的spu_name也需保持表述一致，禁止出现同一SPU同时标注“12 promax”和“12promax”这类格式不统一的情况，需统一为规范表述。
   {
        "produce_name": "郎酒 红花郎15",
        "brand": "郎酒",
        "spu_name": "郎酒 红花郎15",
        "current": 1
    },
                """
            },
            {
                "role": "user",
                "content":  f"""需要处理的商品列表：{product_list} 本次查询的关键词为：{keyword}"""
            }
        ],
        "thinking": {
            "type": "disabled"
        },
        "max_tokens": 16384,
        "temperature": 0,
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "product_relation_result",
                "strict": True,
                "schema": {
                    "type": "object",
                    "properties": {
                        "product_words": {
                            "type": "array",
                            "description": "商品识别和关联判断结果",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "produce_name": {
                                        "type": "string",
                                        "description": "商品完整原始名称",
                                    },
                                    "brand": {
                                        "type": "string",
                                        "description": "商品所属品牌",
                                    },
                                    "spu_name": {
                                        "type": "string",
                                        "description": "去除品牌后的标准SPU名称",
                                    },
                                    "current": {
                                        "type": "integer",
                                        "enum": [0, 1],
                                        "description": "与查询关键词有关为1，否则为0",
                                    },
                                },
                                "required": [
                                    "produce_name",
                                    "brand",
                                    "spu_name",
                                    "current",
                                ],
                                "additionalProperties": False,
                            },
                        },
                    },
                    "required": ["product_words"],
                    "additionalProperties": False,
                },
            },
        },
    }

    headers = {
        'Authorization': 'Bearer ark-7afc3be2-37a8-47fd-9f02-996258a3d305-27da0',
        'Content-Type': 'application/json'
    }
    content  =""
    try:
        response = requests.post(
            url,
            headers=headers,
            json=payload,
            timeout=120,
        )
        response.raise_for_status()

        response_data = response.json()
        content = (
            response_data
            .get("choices", [{}])[0]
            .get("message", {})
            .get("content")
        )

        if not content:
            return []

        if isinstance(content, str):
            content = json.loads(content)

        product_words = content.get("product_words", [])
        print(product_words if isinstance(product_words, list) else [])
        return product_words if isinstance(product_words, list) else []

    except Exception as e:
        return  []

def ai_get_product_list_search(product_list):
    url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
    payload = json.dumps({
        "model": "doubao-seed-2-0-mini-260428",
        "messages": [
            {
                "role": "system",
                "content": """
你是一名信息抽取助手。
你的任务是：从给定名称列表中，删除“只有品牌名、没有具体产品名”的项，只保留“具体产品名称”。

【判定规则】
1. 仅保留能明确指向某个具体商品/产品的名称。
2. 纯品牌名、公司名、系列名，不保留。
3. 如果名称中同时包含“品牌 + 产品名”，要整体保留，因为它属于具体产品名称。
4. 不要因为前面带品牌名就删除，只要后面有明确产品名，就保留完整原文。
5. 输出时保持原顺序，不改写，不拆分，不补充，不去重。
6. 最终只返回保留后的列表，不要输出解释。
                """
            },
            {
                "role": "user",
                "content": f"名称列表:{product_list}"
            }
        ],
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "brand_list",
                "strict": True,
                "schema": {
                    "type": "object",
                    "properties": {
                        "brands": {
                            "type": "array",
                            "items": {
                                "type": "string"
                            },
                            "description": "品牌产品词列表"
                        }
                    },
                    "required": [
                        "brands"
                    ],
                    "additionalProperties": False
                }
            }
        },
        "thinking": {
            "type": "disabled"
        },
        "temperature": 0
    })
    headers = {
        'Authorization': 'Bearer ark-7afc3be2-37a8-47fd-9f02-996258a3d305-27da0',
        'Content-Type': 'application/json'
    }

    response = requests.request("POST", url, headers=headers, data=payload)
    try:
        return json.loads(response.json().get("choices")[0].get("message").get("content")).get("brands")

    except Exception as e:
        return []

def ai_get_brand_name(content):
    url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"

    # 修正JSON Schema定义：array类型需配置items，同时调整最终期望返回字典格式的提示
    payload = json.dumps({
        "model": "doubao-seed-2-0-mini-260428",
        "messages": [
            {
                "role": "system",
                "content": """
###
假如你是品牌识别专家，你将根据用户输入的词汇列表，来解决识别该词汇对应品牌的任务。根据以下规则一步步执行：
1. 若输入词汇能明确对应某个已知品牌，**优先返回该品牌的标准中文名称**，无标准中文名称时再返回品牌英文名称；
2. 若输入词汇无法对应任何已知品牌，则返回“没有品牌”；
3. 无论用户输入的是中文词汇还是英文品牌名，均按上述规则匹配返回（英文品牌名先匹配对应中文，无中文则返回原英文）。
4。用户可能输入的是某个产品线产品词 也返回对应的品牌名称

参考例子：
示例1：
输入：苹果
输出：苹果

示例2：
输入：桌子
输出：没有品牌

示例3：
输入：Nike
输出：耐克

示例4：
输入：Google
输出：谷歌

示例5：
输入：Tesla
输出：特斯拉

示例6：
输入：Zara
输出：飒拉

示例7：
输入：Noname
输出：没有品牌

请回答问题：
输入：XXX
输出：

要求：
1 直接返回识别结果，无需额外解释；
2 严格遵循“先中文、无中文再英文”的返回原则，每一个识别结果均需执行此规则；
3 若输入词汇包含“品牌名 + 业务/产品线/场景词”（如：打车/外卖/出行/优选/买菜/闪购/到家/酒店/团购/买药/骑行/配送/同城/快送/生活/钱包/支付/商城/直播/短视频/云/地图等），则忽略业务词，输出其母品牌标准中文名
4 若识别到品牌按规则返回对应名称，否则返回“没有品牌”。
###
                
                """
            },
            {
                "role": "user",
                "content": str(content)
            }
        ],
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "brand_recognize_result",
                "strict": True,  # 严格模式，禁止返回schema外的字段
                "schema": {
                    "type": "object",
                    "properties": {
                        "results": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "keyword": {"type": "string"},
                                    "brand": {"type": "string"},
                                },
                                "required": ["keyword", "brand"],
                                "additionalProperties": False,
                            }
                        }
                    },
                    "required": ["results"],
                    "additionalProperties": False  # 禁止添加额外字段
                }
            }
        },
        "thinking": {
            "type": "disabled"
        },
        "temperature": 0
    })
    headers = {
        'Authorization': 'Bearer ark-7afc3be2-37a8-47fd-9f02-996258a3d305-27da0',
        'Content-Type': 'application/json'
    }

    # 发送POST请求
    response = requests.request("POST", url, headers=headers, data=payload)
    try:
        response_data = json.loads(response.text)
        result_json_str = response_data["choices"][0]["message"]["content"]
    except (json.JSONDecodeError, KeyError) as e:
        return []
    #
    try:
        brand_emotion_dict = json.loads(result_json_str).get('results')
    except json.JSONDecodeError as e:
        return []

    # 步骤3：返回最终的字典结果
    return brand_emotion_dict

def ai_get_product_sentiment_and_mentions(brand_list, ai_answer_text):


    url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"

    system_prompt = """
你是一位专业的产品舆情与GEO内容分析专家。
你需要根据「AI回答内容」对「指定产品列表」逐个输出每个品牌的：

1) sentiment：情感倾向，仅允许 ["正向","负向","中性"]
   - 若无法判断，必须输出 "中性"
   - sentiment 必须与该品牌在文本中的语境一致，禁止凭空编造

2) positive_mentions：该品牌在文本中的正面提及词/短语 例如：优势、好评、推荐、认可、亮点、强项、信任等
3) negative_mentions：该品牌在文本中的负面提及词/短语 例如：缺点、差评、风险、争议、吐槽、弱点等


强制规则：
- 必须覆盖产品列表中的每个产品，输出 items 数组长度必须等于产品列表长度，并尽量保持顺序一致
- 只提取文本中真实出现或可直接归纳的词/短语，禁止编造
- 必须与该产品直接相关（若无法指向产品，则不要提取）
- 每条词/短语严格 2~10 个字
- 同义词合并去重（如“舒服/舒适”只保留更标准的）
- 若某品牌在文本中没有可提取内容，则 positive_mentions / negative_mentions 返回空数组 []
- 输出必须严格符合 JSON Schema
- 仅输出纯 JSON，不要输出任何额外文字
""".strip()

    payload = {
        "model": "doubao-seed-2-0-mini-260428",
        "messages": [
            {"role": "system", "content": system_prompt},
            {
                "role": "user",
                "content": f"产品列表：{json.dumps(brand_list, ensure_ascii=False)}\n\nAI回答内容：{ai_answer_text}"
            }
        ],
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "brand_sentiment_and_mentions",
                "strict": True,
                "schema": {
                    "type": "object",
                    "properties": {
                        "items": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "brand_name": {"type": "string"},
                                    "sentiment": {
                                        "type": "string",
                                        "enum": ["正向", "负向", "中性"]
                                    },
                                    "positive_mentions": {
                                        "type": "array",
                                        "items": {"type": "string"}
                                    },
                                    "negative_mentions": {
                                        "type": "array",
                                        "items": {"type": "string"}
                                    }
                                },
                                "required": ["brand_name", "sentiment", "positive_mentions", "negative_mentions"],
                                "additionalProperties": False
                            }
                        }
                    },
                    "required": ["items"],
                    "additionalProperties": False
                }
            }
        },
        "thinking": {"type": "disabled"},
        "temperature": 0,
        "top_p": 1
    }

    headers = {
        "Authorization": "Bearer ark-7afc3be2-37a8-47fd-9f02-996258a3d305-27da0",
        "Content-Type": "application/json"
    }

    try:
        resp = requests.post(url, headers=headers, data=json.dumps(payload), timeout=60)
        resp.raise_for_status()
        data = resp.json()

        content = data["choices"][0]["message"]["content"]
        parsed = json.loads(content)

        items = parsed.get("items", [])
        if not isinstance(items, list):
            return _fallback_items(brand_list)

        # ✅ 兜底：保证每个品牌都有一条（并且按 brand_list 顺序）
        mapped = {i.get("brand_name"): i for i in items if isinstance(i, dict)}
        out = []
        for b in brand_list:
            it = mapped.get(b) or {"brand_name": b, "sentiment": "中性", "positive_mentions": [],
                                   "negative_mentions": []}
            # 进一步强兜底字段
            out.append({
                "brand_name": it.get("brand_name", b),
                "sentiment": it.get("sentiment", "中性") if it.get("sentiment") in ("正向", "负向", "中性") else "中性",
                "positive_mentions": it.get("positive_mentions") if isinstance(it.get("positive_mentions"),
                                                                               list) else [],
                "negative_mentions": it.get("negative_mentions") if isinstance(it.get("negative_mentions"),
                                                                               list) else [],
            })
        return out

    except Exception:
        return _fallback_items(brand_list)

def ai_get_brand_sentiment_and_mentions(brand_list, ai_answer_text):


    url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"

    system_prompt = """
你是一位专业的品牌舆情与GEO内容分析专家。
你需要根据「AI回答内容」对「指定品牌列表」逐个输出每个品牌的：

1) sentiment：情感倾向，仅允许 ["正向","负向","中性"]
   - 若无法判断，必须输出 "中性"
   - sentiment 必须与该品牌在文本中的语境一致，禁止凭空编造

2) positive_mentions：该品牌在文本中的正面提及词/短语 例如：优势、好评、推荐、认可、亮点、强项、信任等
3) negative_mentions：该品牌在文本中的负面提及词/短语 例如：缺点、差评、风险、争议、吐槽、弱点等


强制规则：
- 必须覆盖品牌列表中的每个品牌，输出 items 数组长度必须等于品牌列表长度，并尽量保持顺序一致
- 只提取文本中真实出现或可直接归纳的词/短语，禁止编造
- 必须与该品牌直接相关（若无法指向品牌，则不要提取）
- 每条词/短语严格 2~10 个字
- 同义词合并去重（如“舒服/舒适”只保留更标准的）
- 若某品牌在文本中没有可提取内容，则 positive_mentions / negative_mentions 返回空数组 []
- 输出必须严格符合 JSON Schema
- 仅输出纯 JSON，不要输出任何额外文字
""".strip()

    payload = {
        "model": "doubao-seed-2-0-mini-260428",
        "messages": [
            {"role": "system", "content": system_prompt},
            {
                "role": "user",
                "content": f"品牌列表：{json.dumps(brand_list, ensure_ascii=False)}\n\nAI回答内容：{ai_answer_text}"
            }
        ],
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "brand_sentiment_and_mentions",
                "strict": True,
                "schema": {
                    "type": "object",
                    "properties": {
                        "items": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "brand_name": {"type": "string"},
                                    "sentiment": {
                                        "type": "string",
                                        "enum": ["正向", "负向", "中性"]
                                    },
                                    "positive_mentions": {
                                        "type": "array",
                                        "items": {"type": "string"}
                                    },
                                    "negative_mentions": {
                                        "type": "array",
                                        "items": {"type": "string"}
                                    }
                                },
                                "required": ["brand_name", "sentiment", "positive_mentions", "negative_mentions"],
                                "additionalProperties": False
                            }
                        }
                    },
                    "required": ["items"],
                    "additionalProperties": False
                }
            }
        },
        "thinking": {"type": "disabled"},
        "temperature": 0,
        "top_p": 1
    }

    headers = {
        "Authorization": "Bearer ark-7afc3be2-37a8-47fd-9f02-996258a3d305-27da0",
        "Content-Type": "application/json"
    }

    try:
        resp = requests.post(url, headers=headers, data=json.dumps(payload), timeout=60)
        resp.raise_for_status()
        data = resp.json()

        content = data["choices"][0]["message"]["content"]
        parsed = json.loads(content)

        items = parsed.get("items", [])
        if not isinstance(items, list):
            return _fallback_items(brand_list)

        # ✅ 兜底：保证每个品牌都有一条（并且按 brand_list 顺序）
        mapped = {i.get("brand_name"): i for i in items if isinstance(i, dict)}
        out = []
        for b in brand_list:
            it = mapped.get(b) or {"brand_name": b, "sentiment": "中性", "positive_mentions": [],
                                   "negative_mentions": []}
            # 进一步强兜底字段
            out.append({
                "brand_name": it.get("brand_name", b),
                "sentiment": it.get("sentiment", "中性") if it.get("sentiment") in ("正向", "负向", "中性") else "中性",
                "positive_mentions": it.get("positive_mentions") if isinstance(it.get("positive_mentions"),
                                                                               list) else [],
                "negative_mentions": it.get("negative_mentions") if isinstance(it.get("negative_mentions"),
                                                                               list) else [],
            })
        return out

    except Exception:
        return _fallback_items(brand_list)


def _fallback_items(brand_list):
    return [{"brand_name": b, "sentiment": "中性", "positive_mentions": [], "negative_mentions": []} for b in
            brand_list]

def ai_get_product_and_brands(content, prompt):
    url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
    payload = json.dumps({
        "model": "doubao-seed-2-0-mini-260428",
        "messages": [
            {
                "role": "system",
                "content": """
                # 角色
你是一名GEO语义解析专家。
你的任务是：根据输入的问题和对话内容，提取对话中与该问题直接相关的产品词，并为每个产品词匹配对应的品牌词。
# 任务目标
从对话中识别所有被提及的具体产品，但只保留与当前问题语义直接相关的产品。
如果某些产品虽然出现在对话中，但只是顺带提及、与问题讨论主题无关，则不要提取。
# 输入内容
用户会提供：
1）{{question}}（监测问题）
2）{{answer_text}}（AI回答正文）
# 提取规则
1. 产品词定义
产品词是指对话中出现的、能够指向具体商品或具体产品名称的表达，例如：
品牌 + 产品名：如“SK2神仙水”“兰蔻小黑瓶”
产品别名 / 常用名：如“神仙水”
明确可识别品牌归属的具体产品表达
2. 品牌词定义 
品牌词是该产品所属的品牌名称。
输出时需尽量统一为标准品牌名。
3. 相关性判断规则（定义）
只提取与question直接相关的产品词。
应提取
当该产品属于以下情况之一时，可以提取：
是问题中明确询问、比较、推荐、评价、选择的对象
与问题讨论的品类、功效、使用场景、购买决策直接相关
在回答中作为该问题的候选产品、对比产品、推荐产品出现
不应提取
当该产品属于以下情况时，不要提取：
虽然出现在对话里，但和问题主题无关
只是附带提到的物流、平台、服务、赠品、渠道相关内容
与问题讨论的品类、用途、决策场景无直接关系
4. 过滤规则
以下内容一律不提取：
- 无法确认品牌归属的泛产品词：如“面霜”“精华”
- 非产品内容
- 与问题主题无关的产品
5. 去重规则
- 同一产品重复出现时只保留一次
- 优先保留信息更完整的表达
# 输出规则
1. 输出要求
- 先识别所有“产品-品牌”配对，再按品牌分组聚合输出
- 按品牌聚合输出，同一品牌下的多个产品放入同一个 products 数组。
- products 按对话中出现顺序输出。
- 如果某品牌只有一个产品，也仍然使用数组格式。
- 如果没有符合条件的结果，输出空数组 []。
                """
            },
            {
                "role": "user",
                "content": f"监测问题:{prompt}AI回答正文：{content}"
            }
        ],
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "brand_product_list",
                "strict": True,
                "schema": {
                    "type": "object",
                    "properties": {
                        "items": {
                            "type": "array",
                            "description": "按品牌聚合后的产品列表",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "brand": {
                                        "type": "string",
                                        "description": "标准品牌名"
                                    },
                                    "products": {
                                        "type": "array",
                                        "description": "该品牌下与问题直接相关的产品列表，按原文出现顺序输出",
                                        "items": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "required": ["brand", "products"],
                                "additionalProperties": False
                            }
                        }
                    },
                    "required": ["items"],
                    "additionalProperties": False
                }
            }
        },
        "thinking": {
            "type": "disabled"
        },
        "temperature": 0
    })
    headers = {
        'Authorization': 'Bearer ark-7afc3be2-37a8-47fd-9f02-996258a3d305-27da0',
        'Content-Type': 'application/json'
    }

    response = requests.request("POST", url, headers=headers, data=payload)
    try:
        result = response.json()
        content_str = result["choices"][0]["message"]["content"]
        return json.loads(content_str).get("items", [])
    except Exception:
        return []


def ai_get_brand_sentiment_and_mentions_by_articles(brand_name, article_list, batch_size=50):


    normalized_articles = []
    for item in article_list or []:
        if not isinstance(item, dict):
            continue

        source_id = item.get("sourceId")
        if source_id is None:
            continue
        snippet = item.get("snippet")

        # 空值、非字符串、纯空格都不进入大模型
        if not isinstance(snippet, str) or not snippet.strip():
            continue
        normalized_articles.append({
            "sourceId": str(source_id),
            "snippet": snippet.strip()
        })

    if not normalized_articles:
        return []

    all_result_map = {}

    for batch in _chunk_list(normalized_articles, batch_size):
        batch_result = _call_single_batch(brand_name, batch)
        for item in batch_result:
            if not isinstance(item, dict):
                continue

            source_id = item.get("sourceId")
            if source_id is None:
                continue

            all_result_map[str(source_id)] = {
                "sourceId": str(source_id),
                "brand_name": brand_name,
                "sentiment": item.get("sentiment", "中性") if item.get("sentiment") in ("正向", "负向", "中性") else "中性",
                "positive_mentions": item.get("positive_mentions") if isinstance(item.get("positive_mentions"), list) else [],
                "negative_mentions": item.get("negative_mentions") if isinstance(item.get("negative_mentions"), list) else [],
            }

    result = []
    for item in normalized_articles:
        source_id = item["sourceId"]
        result.append(
            all_result_map.get(source_id, {
                "sourceId": source_id,
                "brand_name": brand_name,
                "sentiment": "中性",
                "positive_mentions": [],
                "negative_mentions": []
            })
        )

    return result


def _call_single_batch(brand_name, batch_articles):
    """
    单批调用模型，返回该批文章的分析结果
    """
    url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"

    system_prompt = """
你是一位专业的品牌舆情与GEO内容分析专家。

你会收到：
1）一个品牌名
2）多篇文章，每篇文章都带 sourceId 和 snippet

你的任务是：
分析“这个品牌”在“每一篇文章”中的表现，并输出：

1) sourceId：文章ID，必须与输入一致
2) sentiment：情感倾向，仅允许 ["正向","负向","中性"]
   - 若无法判断，必须输出 "中性"
   - sentiment 必须与该品牌在该文章中的语境一致，禁止凭空编造

3) positive_mentions：该品牌在该文章中的正面提及词/短语
4) negative_mentions：该品牌在该文章中的负面提及词/短语

强制规则：
- 只分析指定品牌，不要分析其他品牌
- 只提取文章中真实出现或可直接归纳的词/短语，禁止编造
- 必须与该品牌直接相关（若无法指向该品牌，则不要提取）
- 每条词/短语严格 2~10 个字
- 同义词合并去重
- 若该品牌在某文章中没有可提取内容，则 positive_mentions / negative_mentions 返回空数组 []
- 返回你能明确判断的文章结果即可
- 输出必须严格符合 JSON Schema
- 仅输出纯 JSON，不要输出任何额外文字
""".strip()

    input_articles = [
        {
            "sourceId": str(item["sourceId"]),
            "snippet": item.get("snippet", "")
        }
        for item in batch_articles
    ]

    payload = {
        "model": "doubao-seed-2-0-mini-260428",
        "messages": [
            {"role": "system", "content": system_prompt},
            {
                "role": "user",
                "content": (
                    f"品牌名：{brand_name}\n\n"
                    f"文章列表：{json.dumps(input_articles, ensure_ascii=False)}"
                )
            }
        ],
        "response_format": {
            "type": "json_schema",
            "json_schema": {
                "name": "single_brand_article_sentiment_and_mentions",
                "strict": True,
                "schema": {
                    "type": "object",
                    "properties": {
                        "items": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "sourceId": {"type": "string"},
                                    "sentiment": {
                                        "type": "string",
                                        "enum": ["正向", "负向", "中性"]
                                    },
                                    "positive_mentions": {
                                        "type": "array",
                                        "items": {"type": "string"}
                                    },
                                    "negative_mentions": {
                                        "type": "array",
                                        "items": {"type": "string"}
                                    }
                                },
                                "required": [
                                    "sourceId",
                                    "sentiment",
                                    "positive_mentions",
                                    "negative_mentions"
                                ],
                                "additionalProperties": False
                            }
                        }
                    },
                    "required": ["items"],
                    "additionalProperties": False
                }
            }
        },
        "thinking": {"type": "disabled"},
        "temperature": 0,
        "top_p": 1
    }

    headers = {
        "Authorization": "Bearer ark-7afc3be2-37a8-47fd-9f02-996258a3d305-27da0",
        "Content-Type": "application/json"
    }

    batch_source_ids = [str(i["sourceId"]) for i in batch_articles]

    try:
        resp = requests.post(url, headers=headers, data=json.dumps(payload), timeout=120)
        resp.raise_for_status()
        data = resp.json()

        content = data["choices"][0]["message"]["content"]
        parsed = json.loads(content)

        items = parsed.get("items", [])
        if not isinstance(items, list):
            return _fallback_single_brand_items(brand_name, batch_source_ids)

        result = []
        seen_source_ids = set()

        for it in items:
            if not isinstance(it, dict):
                continue

            source_id = it.get("sourceId")
            if source_id is None:
                continue

            source_id = str(source_id)

            if source_id not in batch_source_ids:
                continue

            if source_id in seen_source_ids:
                continue
            seen_source_ids.add(source_id)

            result.append({
                "sourceId": source_id,
                "brand_name": brand_name,
                "sentiment": it.get("sentiment", "中性") if it.get("sentiment") in ("正向", "负向", "中性") else "中性",
                "positive_mentions": it.get("positive_mentions") if isinstance(it.get("positive_mentions"), list) else [],
                "negative_mentions": it.get("negative_mentions") if isinstance(it.get("negative_mentions"), list) else [],
            })

        return result

    except Exception:
        return _fallback_single_brand_items(brand_name, batch_source_ids)


def _fallback_single_brand_items(brand_name, source_id_list):
    return [
        {
            "sourceId": str(source_id),
            "brand_name": brand_name,
            "sentiment": "中性",
            "positive_mentions": [],
            "negative_mentions": []
        }
        for source_id in source_id_list
    ]


def _chunk_list(data, batch_size):
    if batch_size <= 0:
        batch_size = 20
    for i in range(0, len(data), batch_size):
        yield data[i:i + batch_size]



if __name__ == '__main__':
    # phone_ = 18900000015
    # begin = '2026-03-22'
    # end = '2026-03-22'
    # req_ids = get_req_id(18900000015,begin,end)

    # 去重，避免 SQL 重复
    # req_ids = list(dict.fromkeys(req_ids))

    # 2. 拼 SQL IN
    # req_ids = ['4f6a9e90-0a4e-49a2-9c6d-4cdb51df317a']
    #
    # req_id_sql = ",".join([f"'{req_id}'" for req_id in req_ids])
    #
    # query_sql = f"select * from geo_commit_task where reqId in ({req_id_sql})"
    # # print(query_sql)
    # query_list = bh_utils.query_data(query_sql)
    #
    # for i in query_list:
    #     prompt =  i.get('prompt')
    #     taskId =  i.get('taskId')
    #     platform =  i.get('platform')
    #     content = get_string_from_tos(f'geo/{taskId}/{platform}/context.txt')
    #     print(f'geo/{taskId}/{platform}/context.txt')\
    content =  """
选购空调需综合考量匹数、能效与核心硬件。1.5匹适合15-20㎡主流卧室[](@mark_underline=1)[citation:8][](@mark_underline=2)。建议认准新一级能效，并优选双排铜管冷凝器与电子膨胀阀组合，制冷快且寿命长[](@mark_underline=3)[citation:5][](@mark_underline=4)[](@mark_underline=5)[citation:11][](@mark_underline=6)。

## 高性价比空调推荐

### 智能省电王：美的 酷省电二代 Pro
[](@mark_underline=7)- **核心配置**：搭载美芝压缩机与1.6排冷凝器，支持AI酷省电2.0算法，APF值达5.8[](@mark_underline=8)[citation:10][](@mark_underline=9)。
- **适用场景**：支持65℃高温稳定运行，适合高频开启的卧室及西晒房间[](@mark_underline=10)[citation:10][](@mark_underline=11)。
- **参考价**：2399元/大1.5匹
[](@mark_underline=12)  [](@replace=media_49e799d7a084e207)
[](@mark_underline=13)[](@mark_underline=14)

### 硬件堆料怪兽：华凌 神机二代 Pro
- **核心配置**：采用纯大双排加长铜管冷凝器与美芝变频压缩机，APF能效比高达6.02[](@mark_underline=15)[citation:10][](@mark_underline=16)。
- **适用场景**：支持68℃极限高温制冷不停机，适合顶楼西晒及对硬件要求高的用户[](@mark_underline=17)[citation:10][](@mark_underline=18)。
[](@mark_underline=19)- **参考价**：2399元/大1.5匹
  [](@replace=media_baedfcb00601cd06)
[](@mark_underline=20)[](@mark_underline=21)

### 基础智能王：小米 巨省电 双排款
- **核心配置**：采用5mm密集排布纯铜细双排冷凝器与海立高效压缩机，APF能效比为5.45[](@mark_underline=22)[citation:10][](@mark_underline=23)。
- **适用场景**：支持温湿双控，米家智能生态全拉满，适合组建全屋智能的年轻家庭[](@mark_underline=24)[citation:10][](@mark_underline=25)。
[](@mark_underline=26)- **参考价**：2099元/1.5匹
  [](@replace=media_4db5d18c4ab39d24)
[](@mark_underline=27)[](@mark_underline=28)

### 主卧睡眠标杆：海尔 净省电 Plus
- **核心配置**：配备海立10.6cc大排量压缩机与大双排48根铜管，冷暖速度极快[](@mark_underline=29)[citation:10][](@mark_underline=30)。
- **适用场景**：内机噪音低至16分贝，配合800m³/h大风量，适合浅眠人群或母婴家庭[](@mark_underline=31)[citation:10][](@mark_underline=32)。
[](@mark_underline=33)- **参考价**：2469元/大1.5匹
  [](@replace=media_afa25a941cf785ed)
[](@mark_underline=34)[](@mark_underline=35)

### 卧室毕业神机：格力 云锦 Ⅲ
- **核心配置**：搭载自研凌达10.3cc压缩机与1.6排加厚铜管，低频运行调校极佳[](@mark_underline=36)[citation:10][](@mark_underline=37)。
- **适用场景**：第二代冷酷外机支持60℃高温制冷，适合预算宽裕且追求十年稳定使用的用户[](@mark_underline=38)[citation:10][](@mark_underline=39)。
[](@mark_underline=40)- **参考价**：3299元/1.5匹
  [](@replace=media_17255e13bbf16ce4)
[](@mark_underline=41)[](@mark_underline=42)

## 选购总结
- **避雷提示**：避开虚标高APF低风量的杂牌机，以及暗藏安装收费的超薄简配外机[](@mark_underline=43)[citation:17][](@mark_underline=44)。
- **决策建议**：看重极致性价比选华凌或小米；注重静音舒适选海尔；追求长期耐用选格力[](@mark_underline=45)[citation:6][](@mark_underline=46)[](@mark_underline=47)[citation:10][](@mark_underline=48)。[](@mark_underline=49)[](@mark_underline=50)

render_ecom_card_widget_jd_start:

[{"replace_media_id": "media_49e799d7a084e207", "sku": "100314462414", "sku_name": "美的 酷省电二代 Pro 大1.5匹", "query": "美的 酷省电二代 Pro", "image_url": "https://img14.360buyimg.com/n1/jfs/t1/393742/37/961/10266/6978d098F9e99409d/00ae1e01e06e584c.jpg", "price": 2399, "shop_name": "美的京东自营旗舰店", "shop_id": "1000282702", "source": "京东", "pc_url": "https://search.jd.com/Search?keyword=%E7%BE%8E%E7%9A%84%20%E9%85%B7%E7%9C%81%E7%94%B5%E4%BA%8C%E4%BB%A3%20Pro%20%E5%A4%A71.5%E5%8C%B9"}, {"replace_media_id": "media_baedfcb00601cd06", "sku": "100167931138", "sku_name": "华凌 神机二代 Pro 大1.5匹", "query": "华凌 神机二代 Pro", "image_url": "https://img14.360buyimg.com/n1/jfs/t1/310290/38/13574/12953/686399b7F4ff0e022/c0b8be2ccab5c0ff.jpg", "price": 2399, "shop_name": "华凌京东自营旗舰店", "shop_id": "1000131362", "source": "京东", "pc_url": "https://search.jd.com/Search?keyword=%E5%8D%8E%E5%87%8C%20%E7%A5%9E%E6%9C%BA%E4%BA%8C%E4%BB%A3%20Pro%20%E5%A4%A71.5%E5%8C%B9"}, {"replace_media_id": "media_4db5d18c4ab39d24", "sku": "100229077089", "sku_name": "小米 巨省电双排 1.5匹挂机", "query": "小米 巨省电 双排款", "image_url": "https://img14.360buyimg.com/n1/jfs/t1/412719/12/11913/3500/69cf7a93F4ef4ef9f/00ae1e01e0e5e9cc.jpg", "price": 2099, "shop_name": "小米京东自营旗舰店", "shop_id": "1000004123", "source": "京东", "pc_url": "https://search.jd.com/Search?keyword=%E5%B0%8F%E7%B1%B3%20%E5%B7%A8%E7%9C%81%E7%94%B5%E5%8F%8C%E6%8E%92%201.5%E5%8C%B9%E6%8C%82%E6%9C%BA"}, {"replace_media_id": "media_afa25a941cf785ed", "sku": "100187484138", "sku_name": "海尔 净省电plus 大1.5匹挂机", "query": "海尔 净省电 Plus", "image_url": "https://img14.360buyimg.com/n1/jfs/t20290112/383157/37/22769/7237/6965fb87F79f70a10/00ae1e01e0239a94.jpg", "price": 2469, "shop_name": "海尔京东自营旗舰店", "shop_id": "1000001782", "source": "京东", "pc_url": "https://search.jd.com/Search?keyword=%E6%B5%B7%E5%B0%94%20%E5%87%80%E7%9C%81%E7%94%B5plus%20%E5%A4%A71.5%E5%8C%B9%E6%8C%82%E6%9C%BA"}, {"replace_media_id": "media_17255e13bbf16ce4", "sku": "100099499558", "sku_name": "格力 云锦Ⅲ 1.5匹挂机", "query": "格力 云锦 Ⅲ", "image_url": "https://img14.360buyimg.com/n1/jfs/t20290108/386303/15/4774/6566/695fd9a6Fde8cbd4d/00ae1e01e0e721c6.jpg", "price": 3299, "shop_name": "格力京东自营旗舰店", "shop_id": "1000003445", "source": "京东", "pc_url": "https://search.jd.com/Search?keyword=%E6%A0%BC%E5%8A%9B%20%E4%BA%91%E9%94%A6%E2%85%A2%201.5%E5%8C%B9%E6%8C%82%E6%9C%BA"}]

render_ecom_card_widget_jd_end:
    """
    product_list  =[
"瓜子二手车","二手车之家"
                  # fcc424e5-58af-494d-9683-5787413a26c9
]
    keyword = """
    瓜子

    
    """
    print(ai_get_product_relation_spu(product_list,keyword))
    # print(ai_result)
    # pro =
    # ai_get_product_list()
