""" GEO诊断服务 - 6大维度检测系统 诊断维度(总分100): - 内容可提取性 (Content Extractability): 20分 - AI能否轻松提取和理解内容 - 实体清晰度 (Entity Clarity): 15分 - AI能否理解品牌是什么 - E-E-A-T信号 (E-E-A-T Signals): 20分 - 经验、专业性、权威性、可信度 - Schema标记 (Schema Markup): 15分 - 结构化数据完整性 - 主题权威 (Topic Authority): 15分 - 品牌在特定领域的权威性 - 引用就绪度 (Citation Readiness): 15分 - 品牌在AI回答中被引用的可能性 """ from __future__ import annotations import logging from dataclasses import dataclass, field logger = logging.getLogger(__name__) # ============================================================ # 诊断数据结构 # ============================================================ @dataclass class DiagnosisItem: """单个诊断项""" name: str # 诊断项名称 status: str # pass/warning/fail description: str # 诊断说明 suggestion: str # 优化建议 score: float = 0.0 # 该项得分 max_score: float = 0.0 # 该项满分 @dataclass class GEODimensionScore: """单个维度的诊断评分详情""" name: str # 维度名称 score: float # 该维度得分 (0-max_score) max_score: float # 该维度满分 items: list[DiagnosisItem] = field(default_factory=list) status: str = "pass" # pass/warning/fail percentage: float = 0.0 # 得分率 (0-100) detail: dict = field(default_factory=dict) @dataclass class GEORecommendation: """优化建议""" priority: str # P0/P1/P2 dimension: str # 所属维度 title: str # 建议标题 description: str # 建议描述 impact: str # 预期影响: high/medium/low effort: str # 实施难度: easy/medium/hard @dataclass class GEODiagnosisResult: """GEO诊断结果""" overall_score: float = 0.0 # 综合评分 0-100 dimensions: list[GEODimensionScore] = field(default_factory=list) recommendations: list[GEORecommendation] = field(default_factory=list) health_level: str = "danger" # excellent/good/pass/danger def __post_init__(self): """计算健康等级""" if self.overall_score >= 80: self.health_level = "excellent" elif self.overall_score >= 60: self.health_level = "good" elif self.overall_score >= 40: self.health_level = "pass" else: self.health_level = "danger" def to_dict(self) -> dict: """转换为字典格式""" return { "overall_score": round(self.overall_score, 2), "health_level": self.health_level, "health_level_label": get_health_level_label(self.health_level), "dimensions": [ { "name": dim.name, "score": round(dim.score, 2), "max_score": dim.max_score, "percentage": round(dim.percentage, 2), "status": dim.status, "items": [ { "name": item.name, "status": item.status, "description": item.description, "suggestion": item.suggestion, "score": round(item.score, 2), "max_score": item.max_score, } for item in dim.items ], "detail": dim.detail, } for dim in self.dimensions ], "recommendations": [ { "priority": rec.priority, "dimension": rec.dimension, "title": rec.title, "description": rec.description, "impact": rec.impact, "effort": rec.effort, } for rec in self.recommendations ], } # ============================================================ # 维度1: 内容可提取性诊断 (满分20) # ============================================================ def diagnose_content_extractability( has_direct_answer: bool = False, has_qa_headings: bool = False, has_structured_data: bool = False, has_internal_links: bool = False, has_freshness_info: bool = False, update_days_ago: int | None = None, ) -> GEODimensionScore: """ 诊断内容可提取性 (满分20) AI需要能够轻松提取和理解内容。 Args: has_direct_answer: 是否有直接回答块(页面首段简洁明确的答案) has_qa_headings: 是否有问答式标题(H2/H3采用问题形式) has_structured_data: 是否使用列表和表格等结构化数据 has_internal_links: 是否有内链到子意图页 has_freshness_info: 是否有内容新鲜度信息(更新日期和作者) update_days_ago: 内容最后更新距今天数 Returns: GEODimensionScore: 内容可提取性维度评分 """ max_score = 20.0 items = [] # 1. 直接回答块 (P0, 6分) direct_answer_score = 6.0 if has_direct_answer else 0.0 items.append(DiagnosisItem( name="直接回答块", status="pass" if has_direct_answer else "fail", description="页面首段是否包含简洁明确的答案,便于AI直接提取", suggestion="在页面首段添加2-3句话的简洁答案,直接回答用户核心问题", score=direct_answer_score, max_score=6.0, )) # 2. 问答式标题 (P0, 5分) qa_headings_score = 5.0 if has_qa_headings else 0.0 items.append(DiagnosisItem( name="问答式标题", status="pass" if has_qa_headings else "fail", description="H2/H3标题是否采用问题形式,帮助AI理解内容结构", suggestion="将关键H2/H3标题改为问题形式,如'什么是X'、'如何使用Y'", score=qa_headings_score, max_score=5.0, )) # 3. 列表和表格 (P0, 4分) structured_score = 4.0 if has_structured_data else 0.0 items.append(DiagnosisItem( name="列表和表格", status="pass" if has_structured_data else "fail", description="是否使用列表、表格等结构化数据展示信息", suggestion="使用HTML列表(ul/ol)和表格(table)组织信息,便于AI解析", score=structured_score, max_score=4.0, )) # 4. 内链到子意图页 (P1, 3分) internal_links_score = 3.0 if has_internal_links else 0.0 items.append(DiagnosisItem( name="内链到子意图页", status="pass" if has_internal_links else "warning", description="是否链接到相关深度内容页面", suggestion="添加内链到相关子话题页面,形成内容网络", score=internal_links_score, max_score=3.0, )) # 5. 内容新鲜度 (P1, 2分) freshness_score = 0.0 freshness_status = "fail" if has_freshness_info: if update_days_ago is not None: if update_days_ago <= 30: freshness_score = 2.0 freshness_status = "pass" elif update_days_ago <= 90: freshness_score = 1.5 freshness_status = "warning" else: freshness_score = 0.5 freshness_status = "warning" else: freshness_score = 1.0 freshness_status = "warning" items.append(DiagnosisItem( name="内容新鲜度", status=freshness_status, description="是否有更新日期和作者信息,体现内容时效性", suggestion="在页面显眼位置展示最后更新日期和作者信息", score=freshness_score, max_score=2.0, )) total_score = sum(item.score for item in items) percentage = (total_score / max_score) * 100 # 维度状态:如果有fail项则为warning,全pass则为pass has_fail = any(item.status == "fail" for item in items) status = "warning" if has_fail else "pass" return GEODimensionScore( name="内容可提取性", score=total_score, max_score=max_score, items=items, status=status, percentage=round(percentage, 2), detail={ "has_direct_answer": has_direct_answer, "has_qa_headings": has_qa_headings, "has_structured_data": has_structured_data, "has_internal_links": has_internal_links, "has_freshness_info": has_freshness_info, "update_days_ago": update_days_ago, }, ) # ============================================================ # 维度2: 实体清晰度诊断 (满分15) # ============================================================ def diagnose_entity_clarity( has_brand_definition: bool = False, has_target_audience: bool = False, has_unique_value: bool = False, has_industry_classification: bool = False, ) -> GEODimensionScore: """ 诊断实体清晰度 (满分15) AI需要能够理解品牌是什么。 Args: has_brand_definition: 是否清晰说明品牌做什么 has_target_audience: 是否明确服务谁 has_unique_value: 是否有差异化价值主张 has_industry_classification: 是否有行业分类信息 Returns: GEODimensionScore: 实体清晰度维度评分 """ max_score = 15.0 items = [] # 1. 品牌定义 (5分) brand_def_score = 5.0 if has_brand_definition else 0.0 items.append(DiagnosisItem( name="品牌定义", status="pass" if has_brand_definition else "fail", description="是否清晰说明品牌做什么,AI理解准确率目标≥95%", suggestion="在首页和About页面添加清晰的品牌定义,包含核心业务和价值主张", score=brand_def_score, max_score=5.0, )) # 2. 目标受众 (4分) audience_score = 4.0 if has_target_audience else 0.0 items.append(DiagnosisItem( name="目标受众", status="pass" if has_target_audience else "fail", description="是否明确服务谁,实体识别准确率目标≥90%", suggestion="明确描述目标用户群体,如'为中小企业提供XX服务'", score=audience_score, max_score=4.0, )) # 3. 差异化价值 (3分) value_score = 3.0 if has_unique_value else 0.0 items.append(DiagnosisItem( name="差异化价值", status="pass" if has_unique_value else "warning", description="为什么选择这个品牌,独特性评分目标≥80", suggestion="突出品牌独特优势,如技术领先、服务优质、价格合理等", score=value_score, max_score=3.0, )) # 4. 行业分类 (3分) industry_score = 3.0 if has_industry_classification else 0.0 items.append(DiagnosisItem( name="行业分类", status="pass" if has_industry_classification else "warning", description="品牌属于什么行业,分类准确率目标≥95%", suggestion="在页面中明确标注行业分类,如'SaaS'、'电子商务'等", score=industry_score, max_score=3.0, )) total_score = sum(item.score for item in items) percentage = (total_score / max_score) * 100 has_fail = any(item.status == "fail" for item in items) status = "warning" if has_fail else "pass" return GEODimensionScore( name="实体清晰度", score=total_score, max_score=max_score, items=items, status=status, percentage=round(percentage, 2), detail={ "has_brand_definition": has_brand_definition, "has_target_audience": has_target_audience, "has_unique_value": has_unique_value, "has_industry_classification": has_industry_classification, }, ) # ============================================================ # 维度3: E-E-A-T信号诊断 (满分20) # ============================================================ def diagnose_eeat_signals( has_author_bio: bool = False, author_credentials_complete: float = 0.0, has_certifications: bool = False, certification_count: int = 0, has_data_sources: bool = False, authoritative_source_ratio: float = 0.0, has_expert_endorsements: bool = False, endorsement_count: int = 0, ) -> GEODimensionScore: """ 诊断E-E-A-T信号 (满分20) AI需要验证品牌的可信度(经验、专业性、权威性、可信度)。 Args: has_author_bio: 是否有作者资质信息 author_credentials_complete: 作者简介完整度 (0-1) has_certifications: 是否有专业认证 certification_count: 认证/奖项数量 has_data_sources: 是否引用数据来源 authoritative_source_ratio: 权威源引用比例 (0-1) has_expert_endorsements: 是否有专家背书 endorsement_count: 专家背书数量 Returns: GEODimensionScore: E-E-A-T信号维度评分 """ max_score = 20.0 items = [] # 1. 作者资质 (6分) author_score = 0.0 if has_author_bio: author_score = author_credentials_complete * 6.0 author_status = "pass" if author_score >= 5.4 else ("warning" if author_score >= 3.0 else "fail") items.append(DiagnosisItem( name="作者资质", status=author_status, description="内容作者是否有专业背景,作者简介完整度目标≥90%", suggestion="添加作者详细简介,包含教育背景、工作经验、专业领域", score=author_score, max_score=6.0, )) # 2. 专业认证 (5分) cert_score = 0.0 if has_certifications: if certification_count >= 5: cert_score = 5.0 elif certification_count >= 3: cert_score = 4.0 elif certification_count >= 1: cert_score = 2.5 cert_status = "pass" if cert_score >= 4.0 else ("warning" if cert_score >= 2.0 else "fail") items.append(DiagnosisItem( name="专业认证", status=cert_status, description="是否有行业认证/奖项,认证展示率目标≥80%", suggestion="展示行业认证、奖项、资质,如ISO认证、行业奖项等", score=cert_score, max_score=5.0, )) # 3. 数据来源 (5分) source_score = authoritative_source_ratio * 5.0 if has_data_sources else 0.0 source_status = "pass" if source_score >= 4.0 else ("warning" if source_score >= 2.0 else "fail") items.append(DiagnosisItem( name="数据来源", status=source_status, description="是否引用可靠数据,引用权威源比例目标≥70%", suggestion="引用权威机构数据,如政府报告、学术研究、行业报告", score=source_score, max_score=5.0, )) # 4. 专家背书 (4分) endorsement_score = 0.0 if has_expert_endorsements: if endorsement_count >= 5: endorsement_score = 4.0 elif endorsement_count >= 3: endorsement_score = 3.0 elif endorsement_count >= 1: endorsement_score = 1.5 endorsement_status = "pass" if endorsement_score >= 3.0 else ("warning" if endorsement_score >= 1.5 else "fail") items.append(DiagnosisItem( name="专家背书", status=endorsement_status, description="是否有行业专家认可,背书数量目标≥3", suggestion="获取行业专家推荐、用户评价、案例研究", score=endorsement_score, max_score=4.0, )) total_score = sum(item.score for item in items) percentage = (total_score / max_score) * 100 has_fail = any(item.status == "fail" for item in items) status = "warning" if has_fail else "pass" return GEODimensionScore( name="E-E-A-T信号", score=total_score, max_score=max_score, items=items, status=status, percentage=round(percentage, 2), detail={ "has_author_bio": has_author_bio, "author_credentials_complete": round(author_credentials_complete, 2), "has_certifications": has_certifications, "certification_count": certification_count, "has_data_sources": has_data_sources, "authoritative_source_ratio": round(authoritative_source_ratio, 2), "has_expert_endorsements": has_expert_endorsements, "endorsement_count": endorsement_count, }, ) # ============================================================ # 维度4: Schema标记诊断 (满分15) # ============================================================ def diagnose_schema_markup( has_organization: bool = False, has_product: bool = False, has_article: bool = False, has_faq: bool = False, has_howto: bool = False, has_breadcrumb: bool = False, ) -> GEODimensionScore: """ 诊断Schema标记完整性 (满分15) 结构化数据帮助AI理解内容。 Args: has_organization: 是否有Organization标记(企业主页) has_product: 是否有Product标记(产品页) has_article: 是否有Article/BlogPosting标记(博客文章) has_faq: 是否有FAQPage标记(常见问题) has_howto: 是否有HowTo标记(操作指南) has_breadcrumb: 是否有BreadcrumbList标记(导航结构) Returns: GEODimensionScore: Schema标记维度评分 """ max_score = 15.0 items = [] # 1. Organization (P0必须, 4分) org_score = 4.0 if has_organization else 0.0 items.append(DiagnosisItem( name="Organization", status="pass" if has_organization else "fail", description="企业主页的Organization标记,包含名称、logo、联系方式", suggestion="添加Organization Schema,包含@type: Organization、name、url、logo", score=org_score, max_score=4.0, )) # 2. Product (P0必须, 3分) product_score = 3.0 if has_product else 0.0 items.append(DiagnosisItem( name="Product", status="pass" if has_product else "fail", description="产品页的Product标记,包含名称、描述、价格、评价", suggestion="为产品页添加Product Schema,包含name、description、offers、aggregateRating", score=product_score, max_score=3.0, )) # 3. Article/BlogPosting (P0必须, 3分) article_score = 3.0 if has_article else 0.0 items.append(DiagnosisItem( name="Article/BlogPosting", status="pass" if has_article else "fail", description="博客文章的Article标记,包含作者、发布日期、摘要", suggestion="为文章添加Article或BlogPosting Schema,包含author、datePublished、headline", score=article_score, max_score=3.0, )) # 4. FAQPage (P1推荐, 2分) faq_score = 2.0 if has_faq else 0.0 items.append(DiagnosisItem( name="FAQPage", status="pass" if has_faq else "warning", description="常见问题的FAQPage标记", suggestion="为FAQ页面添加FAQPage Schema,包含问题和答案对", score=faq_score, max_score=2.0, )) # 5. HowTo (P1推荐, 2分) howto_score = 2.0 if has_howto else 0.0 items.append(DiagnosisItem( name="HowTo", status="pass" if has_howto else "warning", description="操作指南的HowTo标记", suggestion="为教程类内容添加HowTo Schema,包含步骤列表", score=howto_score, max_score=2.0, )) # 6. BreadcrumbList (P1推荐, 1分) breadcrumb_score = 1.0 if has_breadcrumb else 0.0 items.append(DiagnosisItem( name="BreadcrumbList", status="pass" if has_breadcrumb else "warning", description="导航结构的BreadcrumbList标记", suggestion="添加BreadcrumbList Schema,帮助AI理解页面层级关系", score=breadcrumb_score, max_score=1.0, )) total_score = sum(item.score for item in items) percentage = (total_score / max_score) * 100 has_fail = any(item.status == "fail" for item in items) status = "warning" if has_fail else "pass" return GEODimensionScore( name="Schema标记", score=total_score, max_score=max_score, items=items, status=status, percentage=round(percentage, 2), detail={ "has_organization": has_organization, "has_product": has_product, "has_article": has_article, "has_faq": has_faq, "has_howto": has_howto, "has_breadcrumb": has_breadcrumb, "schema_count": sum([ has_organization, has_product, has_article, has_faq, has_howto, has_breadcrumb, ]), }, ) # ============================================================ # 维度5: 主题权威诊断 (满分15) # ============================================================ def diagnose_topic_authority( content_depth_score: float = 0.0, topic_coverage_ratio: float = 0.0, entity_consistency_score: float = 0.0, cluster_completeness: float = 0.0, total_content_count: int = 0, topic_cluster_count: int = 0, ) -> GEODimensionScore: """ 诊断主题权威 (满分15) AI需要验证品牌在特定领域的权威性。 Args: content_depth_score: 内容深度评分 (0-1),目标≥4.6/5即0.92 topic_coverage_ratio: 话题覆盖度 (0-1),目标≥80% entity_consistency_score: 实体信号一致性 (0-1),目标≥85% cluster_completeness: 内链网络集群完整度 (0-1),目标≥70% total_content_count: 总内容数量 topic_cluster_count: 主题集群数量 Returns: GEODimensionScore: 主题权威维度评分 """ max_score = 15.0 items = [] # 1. 内容深度 (5分) depth_score = content_depth_score * 5.0 depth_status = "pass" if content_depth_score >= 0.8 else ("warning" if content_depth_score >= 0.5 else "fail") items.append(DiagnosisItem( name="内容深度", status=depth_status, description="是否全面覆盖主题,内容质量QScore目标≥4.6/5", suggestion="增加内容深度,包含详细解释、案例分析、数据支撑", score=depth_score, max_score=5.0, )) # 2. 话题覆盖度 (4分) coverage_score = topic_coverage_ratio * 4.0 coverage_status = "pass" if topic_coverage_ratio >= 0.8 else ("warning" if topic_coverage_ratio >= 0.5 else "fail") items.append(DiagnosisItem( name="话题覆盖度", status=coverage_status, description="是否覆盖相关子话题,话题覆盖率目标≥80%", suggestion="创建覆盖核心话题及其子话题的内容矩阵", score=coverage_score, max_score=4.0, )) # 3. 实体信号一致性 (3分) consistency_score = entity_consistency_score * 3.0 consistency_status = "pass" if entity_consistency_score >= 0.85 else ("warning" if entity_consistency_score >= 0.6 else "fail") items.append(DiagnosisItem( name="实体信号一致性", status=consistency_status, description="各页面实体信号是否一致,一致性评分目标≥85%", suggestion="确保各页面使用一致的品牌名称、描述、行业分类", score=consistency_score, max_score=3.0, )) # 4. 内链网络 (3分) network_score = cluster_completeness * 3.0 network_status = "pass" if cluster_completeness >= 0.7 else ("warning" if cluster_completeness >= 0.4 else "fail") items.append(DiagnosisItem( name="内链网络", status=network_status, description="是否形成主题内容集群,集群完整度目标≥70%", suggestion="建立主题集群,通过内链将相关内容连接成网络", score=network_score, max_score=3.0, )) total_score = sum(item.score for item in items) percentage = (total_score / max_score) * 100 has_fail = any(item.status == "fail" for item in items) status = "warning" if has_fail else "pass" return GEODimensionScore( name="主题权威", score=total_score, max_score=max_score, items=items, status=status, percentage=round(percentage, 2), detail={ "content_depth_score": round(content_depth_score, 2), "topic_coverage_ratio": round(topic_coverage_ratio, 2), "entity_consistency_score": round(entity_consistency_score, 2), "cluster_completeness": round(cluster_completeness, 2), "total_content_count": total_content_count, "topic_cluster_count": topic_cluster_count, }, ) # ============================================================ # 维度6: 引用就绪度诊断 (满分15) # ============================================================ def diagnose_citation_readiness( answer_ownership_rate: float = 0.0, citation_accuracy: float = 0.0, ai_sov: float = 0.0, competitor_gap: float = 0.0, total_ai_responses: int = 0, brand_mention_count: int = 0, accurate_citation_count: int = 0, ) -> GEODimensionScore: """ 诊断引用就绪度 (满分15) 评估品牌在AI回答中被引用的可能性。 Args: answer_ownership_rate: AOR - Answer Ownership Rate (0-1),目标≥50% citation_accuracy: 引用准确率 (0-1),目标≥90% ai_sov: AI Share of Voice (0-1),目标≥30% competitor_gap: 与竞品差距 (pp),目标≤10pp total_ai_responses: AI回答总数 brand_mention_count: 品牌被提及次数 accurate_citation_count: 准确引用次数 Returns: GEODimensionScore: 引用就绪度维度评分 """ max_score = 15.0 items = [] # 1. 引用频率 AOR (5分) aor_score = 0.0 if answer_ownership_rate >= 0.5: aor_score = 5.0 elif answer_ownership_rate >= 0.3: aor_score = 3.5 elif answer_ownership_rate >= 0.1: aor_score = 2.0 else: aor_score = answer_ownership_rate * 10.0 aor_status = "pass" if answer_ownership_rate >= 0.5 else ("warning" if answer_ownership_rate >= 0.2 else "fail") items.append(DiagnosisItem( name="引用频率 (AOR)", status=aor_status, description="品牌在AI回答中被提及的频率,AOR目标≥50%", suggestion="优化内容结构,提高被AI引用的概率", score=aor_score, max_score=5.0, )) # 2. 引用质量 (4分) accuracy_score = citation_accuracy * 4.0 accuracy_status = "pass" if citation_accuracy >= 0.9 else ("warning" if citation_accuracy >= 0.7 else "fail") # 确保满分时得到满分 if citation_accuracy >= 1.0: accuracy_score = 4.0 items.append(DiagnosisItem( name="引用质量", status=accuracy_status, description="引用内容是否准确完整,引用准确率目标≥90%", suggestion="确保内容准确无误,避免过时或错误信息", score=accuracy_score, max_score=4.0, )) # 3. AI声量占比 (3分) sov_score = 0.0 if ai_sov >= 0.3: sov_score = 3.0 elif ai_sov >= 0.15: sov_score = 2.0 elif ai_sov >= 0.05: sov_score = 1.0 else: sov_score = ai_sov * 10.0 sov_status = "pass" if ai_sov >= 0.3 else ("warning" if ai_sov >= 0.1 else "fail") items.append(DiagnosisItem( name="AI声量占比", status=sov_status, description="品牌在AI回答中的占比,AI SOV目标≥30%", suggestion="增加品牌曝光,提高在AI回答中的出现频率", score=sov_score, max_score=3.0, )) # 4. 竞品对比 (3分) gap_score = 0.0 if competitor_gap <= 0.1: gap_score = 3.0 elif competitor_gap <= 0.2: gap_score = 2.0 elif competitor_gap <= 0.3: gap_score = 1.0 else: gap_score = max(0.0, 3.0 - competitor_gap * 5) # 确保差距为0时得满分,差距过大时得0分 if competitor_gap <= 0.0: gap_score = 3.0 if competitor_gap >= 0.6: gap_score = 0.0 gap_status = "pass" if competitor_gap <= 0.1 else ("warning" if competitor_gap <= 0.25 else "fail") items.append(DiagnosisItem( name="竞品对比", status=gap_status, description="与竞品在AI回答中的表现差距,差距目标≤10pp", suggestion="分析竞品优势,针对性优化内容策略", score=gap_score, max_score=3.0, )) total_score = sum(item.score for item in items) percentage = (total_score / max_score) * 100 has_fail = any(item.status == "fail" for item in items) status = "warning" if has_fail else "pass" return GEODimensionScore( name="引用就绪度", score=total_score, max_score=max_score, items=items, status=status, percentage=round(percentage, 2), detail={ "answer_ownership_rate": round(answer_ownership_rate, 2), "citation_accuracy": round(citation_accuracy, 2), "ai_sov": round(ai_sov, 2), "competitor_gap": round(competitor_gap, 2), "total_ai_responses": total_ai_responses, "brand_mention_count": brand_mention_count, "accurate_citation_count": accurate_citation_count, }, ) # ============================================================ # 推荐生成 # ============================================================ def generate_recommendations(dimensions: list[GEODimensionScore]) -> list[GEORecommendation]: """ 根据诊断结果生成优化建议 Args: dimensions: 各维度诊断结果 Returns: list[GEORecommendation]: 优化建议列表 """ recommendations = [] for dim in dimensions: for item in dim.items: if item.status == "fail": priority = "P0" impact = "high" elif item.status == "warning": priority = "P1" impact = "medium" else: continue # 根据诊断项确定实施难度 effort = "medium" if "Schema" in item.name or "标记" in item.name: effort = "easy" elif "内容深度" in item.name or "话题覆盖" in item.name: effort = "hard" recommendations.append(GEORecommendation( priority=priority, dimension=dim.name, title=f"优化: {item.name}", description=item.suggestion, impact=impact, effort=effort, )) # 按优先级排序 priority_order = {"P0": 0, "P1": 1, "P2": 2} recommendations.sort(key=lambda r: priority_order.get(r.priority, 3)) return recommendations # ============================================================ # 工具函数 # ============================================================ def get_health_level(score: float) -> str: """ 根据评分获取健康等级 80+ -> excellent (优秀/绿) 60-79 -> good (良好/黄) 40-59 -> pass (及格/橙) <40 -> danger (危险/红) """ if score >= 80: return "excellent" if score >= 60: return "good" if score >= 40: return "pass" return "danger" def get_health_level_label(level: str) -> str: """获取健康等级中文标签""" labels = { "excellent": "优秀", "good": "良好", "pass": "及格", "danger": "危险", } return labels.get(level, "未知") # ============================================================ # GEODiagnosisService 服务类 # ============================================================ @dataclass class GEODiagnosisInput: """GEO诊断输入参数""" # 内容可提取性 has_direct_answer: bool = False has_qa_headings: bool = False has_structured_data: bool = False has_internal_links: bool = False has_freshness_info: bool = False update_days_ago: int | None = None # 实体清晰度 has_brand_definition: bool = False has_target_audience: bool = False has_unique_value: bool = False has_industry_classification: bool = False # E-E-A-T信号 has_author_bio: bool = False author_credentials_complete: float = 0.0 has_certifications: bool = False certification_count: int = 0 has_data_sources: bool = False authoritative_source_ratio: float = 0.0 has_expert_endorsements: bool = False endorsement_count: int = 0 # Schema标记 has_organization: bool = False has_product: bool = False has_article: bool = False has_faq: bool = False has_howto: bool = False has_breadcrumb: bool = False # 主题权威 content_depth_score: float = 0.0 topic_coverage_ratio: float = 0.0 entity_consistency_score: float = 0.0 cluster_completeness: float = 0.0 total_content_count: int = 0 topic_cluster_count: int = 0 # 引用就绪度 answer_ownership_rate: float = 0.0 citation_accuracy: float = 0.0 ai_sov: float = 0.0 competitor_gap: float = 0.0 total_ai_responses: int = 0 brand_mention_count: int = 0 accurate_citation_count: int = 0 class GEODiagnosisService: """GEO诊断服务""" def diagnose(self, input_data: GEODiagnosisInput) -> GEODiagnosisResult: """ 执行GEO诊断 Args: input_data: 诊断输入参数 Returns: GEODiagnosisResult: 诊断结果 """ # 1. 内容可提取性诊断 (20分) content_extractability = diagnose_content_extractability( has_direct_answer=input_data.has_direct_answer, has_qa_headings=input_data.has_qa_headings, has_structured_data=input_data.has_structured_data, has_internal_links=input_data.has_internal_links, has_freshness_info=input_data.has_freshness_info, update_days_ago=input_data.update_days_ago, ) # 2. 实体清晰度诊断 (15分) entity_clarity = diagnose_entity_clarity( has_brand_definition=input_data.has_brand_definition, has_target_audience=input_data.has_target_audience, has_unique_value=input_data.has_unique_value, has_industry_classification=input_data.has_industry_classification, ) # 3. E-E-A-T信号诊断 (20分) eeat_signals = diagnose_eeat_signals( has_author_bio=input_data.has_author_bio, author_credentials_complete=input_data.author_credentials_complete, has_certifications=input_data.has_certifications, certification_count=input_data.certification_count, has_data_sources=input_data.has_data_sources, authoritative_source_ratio=input_data.authoritative_source_ratio, has_expert_endorsements=input_data.has_expert_endorsements, endorsement_count=input_data.endorsement_count, ) # 4. Schema标记诊断 (15分) schema_markup = diagnose_schema_markup( has_organization=input_data.has_organization, has_product=input_data.has_product, has_article=input_data.has_article, has_faq=input_data.has_faq, has_howto=input_data.has_howto, has_breadcrumb=input_data.has_breadcrumb, ) # 5. 主题权威诊断 (15分) topic_authority = diagnose_topic_authority( content_depth_score=input_data.content_depth_score, topic_coverage_ratio=input_data.topic_coverage_ratio, entity_consistency_score=input_data.entity_consistency_score, cluster_completeness=input_data.cluster_completeness, total_content_count=input_data.total_content_count, topic_cluster_count=input_data.topic_cluster_count, ) # 6. 引用就绪度诊断 (15分) citation_readiness = diagnose_citation_readiness( answer_ownership_rate=input_data.answer_ownership_rate, citation_accuracy=input_data.citation_accuracy, ai_sov=input_data.ai_sov, competitor_gap=input_data.competitor_gap, total_ai_responses=input_data.total_ai_responses, brand_mention_count=input_data.brand_mention_count, accurate_citation_count=input_data.accurate_citation_count, ) # 汇总维度 dimensions = [ content_extractability, entity_clarity, eeat_signals, schema_markup, topic_authority, citation_readiness, ] # 计算综合评分 overall_score = sum(dim.score for dim in dimensions) overall_score = round(min(100.0, max(0.0, overall_score)), 2) # 生成推荐 recommendations = generate_recommendations(dimensions) return GEODiagnosisResult( overall_score=overall_score, dimensions=dimensions, recommendations=recommendations, ) def diagnose_from_dict(self, data: dict) -> GEODiagnosisResult: """ 从字典执行GEO诊断(便捷方法) Args: data: 诊断参数字典 Returns: GEODiagnosisResult: 诊断结果 """ input_data = GEODiagnosisInput(**data) return self.diagnose(input_data)