from django.shortcuts import render from .models import Work, Fingerprint from .serializers import WorkSerializer, WorkCreateSerializer, WorkDqCalSerializer, WorkDhCalSerializer from apps.utils.viewsets import CustomModelViewSet from rest_framework.decorators import action import os from django.conf import settings import json from apps.carbon.service import parse_file, get_fingerprint, hamming_distance, split_simhash import requests from rest_framework.exceptions import ParseError import re from rest_framework.response import Response from django.db import transaction, IntegrityError from django.db.models import Q # Create your views here. LLM_URL = "http://106.0.4.200:9000/v1/chat/completions" API_KEY = "JJVAide0hw3eaugGmxecyYYFw45FX2LfhnYJtC+W2rw" MODEL = "Qwen/QwQ-32B" HEADERS = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } HAMMING_THRESHOLD = 5 # 指纹相似阈值,可调 def get_standard(): standard_path = os.path.join(settings.BASE_DIR, 'apps', 'carbon', 'standard.json') with open(standard_path, 'r', encoding='utf-8') as f: standard_content = json.load(f) return standard_content def ask(input:str, p_name:str, stream=False): promot_path = os.path.join(settings.BASE_DIR, 'apps', 'carbon', 'promot', f"{p_name}.md") with open(promot_path, "r", encoding="utf-8") as f: promot_str = f.read() his = [{"role":"system", "content": promot_str}] his.append({"role":"user", "content": input}) payload = { "model": MODEL, "messages": his, "temperature": 0, "stream": stream, "chat_template_kwargs": {"enable_thinking": False} } response = requests.post(LLM_URL, headers=HEADERS, json=payload, stream=stream, timeout=(60, 240)) if not stream: if response.json().get("detail") == "Internal server error": raise ParseError("模型处理错误超过最大token限制") return response.json()["choices"][0]["message"]["content"] def simhash_to_db(n: int) -> int: return n if n < (1 << 63) else n - (1 << 64) def simhash_from_db(n: int) -> int: return n if n >= 0 else n + (1 << 64) class WorkViewSet(CustomModelViewSet): queryset = Work.objects.all() serializer_class = WorkSerializer create_serializer_class = WorkCreateSerializer search_fields = ['name', "description"] @action(methods=['get'], detail=False, perms_map={'get': '*'}) def my(self, request, *args, **kwargs): user = self.request.user queryset = self.filter_queryset(self.get_queryset().filter(create_by=user)) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post'], serializer_class=WorkDqCalSerializer) @transaction.atomic def cal_dq(self, request, pk): work = self.get_object() sr = WorkDqCalSerializer(work, data=request.data) sr.is_valid(raise_exception=True) sr.save() total_score = 0 work = Work.objects.get(pk=pk) # 开始计算贷前评分和指纹 data = get_standard() if work.dq_file1: for item in data: if item["thirdLevel"] in [ "碳中和路线图", "短期/中期/长期减碳目标", "设立碳管理相关部门", "气候相关风险评估机制", "内部碳定价机制", "碳管理数字化平台建设", "碳交易与履约能力", "CCER等减排项目开发管理", "数字化碳管理平台", ]: item["result"] = item["scoringCriteria"][0]["选项"] item["score"] = item["fullScore"] total_score += item["score"] if work.dq_file2: for item in data: if item["thirdLevel"] in [ "能源与碳排放管理体系", "碳排放数据监测、报告与核查", "参与权威信息平台披露", "碳中和目标与进展经第三方认证", "碳排放实时监测覆盖率达标", "数据自动化采集比例达标", "数据质量与校验机制", ]: item["result"] = item["scoringCriteria"][0]["选项"] item["score"] = item["fullScore"] total_score += item["score"] if work.dq_file3: for item in data: if item["thirdLevel"] in [ "ESG报告", "工业固废/生物质资源利用率数据", "硫化物减排措施", "氮氧化物减排措施", "其他污染物减排措施", "项目选址生态避让与保护", "矿山生态修复与复垦方案", "厂区绿化与生态碳汇措施", "低碳产品认证与标识", "产品耐久性与回收性设计", "无环保处罚与信访记录", "环境应急管理体系", "员工健康安全管理体系与制度", "符合标准的物理环境与防护措施", "员工心理健康支持计划", "社区沟通与透明度机制", "社区经济与发展贡献措施", "社区负面影响缓解措施", "供应商行为准则", "供应商筛查与评估机制", "供应商审核与改进机制", "完善的治理结构", "商业道德与反腐败制度", ]: item["result"] = item["scoringCriteria"][0]["选项"] item["score"] = item["fullScore"] total_score += item["score"] if work.dq_file4: for item in data: if item["thirdLevel"] in [ "资金分配明细", "资本金比例与到位证明", "融资渠道多样性", "成本效益分析", "碳减排收益量化", "社会效益评估", "风险管控方案", "关键风险应对策略与预案", "金融机构或第三方风险分担机制", "绿色金融资质认证与资金用途", "融资条款与ESG绩效挂钩", "国际合作资金申请与利用", "应急响应与能力建设机制", ]: item["result"] = item["scoringCriteria"][0]["选项"] item["score"] = item["fullScore"] total_score += item["score"] if work.dq_file5: for item in data: if item["thirdLevel"] in [ "AI预测减碳潜力应用", "智能优化控制算法应用", "ERP/EMS/MES系统集成度达标", "IoT设备覆盖率达标", "跨系统数据协同能力", "碳数据安全管理措施", "系统抗攻击能力达标", "数据合规性与审计追踪机制", ]: item["result"] = item["scoringCriteria"][0]["选项"] item["score"] = item["fullScore"] total_score += item["score"] if work.dq_file6: path = (settings.BASE_DIR + work.dq_file6.path).replace('\\', '/') file_content = parse_file(path) if file_content: res = ask(f'以下内容为用户报告: {file_content}', "tec") if res == "是": for item in data: if item["firstLevel"] == "二、技术路径(35 分)": item["result"] = item["scoringCriteria"][0]["选项"] item["score"] = item["fullScore"] total_score += item["score"] if work.dq_file1: path = (settings.BASE_DIR + work.dq_file1.path).replace('\\', '/') content = parse_file(path) if content: if bool(re.search(r'碳?减排目标', content)): data[3]["result"] = "有" data[3]["score"] = data[3]["fullScore"] total_score += data[3]["score"] def cal_percent(decline_patterns, content, data, index, total_score): decline_percent = None for pattern in decline_patterns: match = re.search(pattern, content, re.DOTALL) if match: decline_percent = float(match.group(1)) break if decline_percent: if decline_percent >= 10: data[index]["result"] = 3 data[index]["score"] = 5 elif decline_percent >= 5: data[index]["result"] = 2 data[index]["score"] = 2.5 elif decline_percent > 0: data[index]["result"] = 1 data[index]["score"] = 1.5 total_score += data[index].get("score", 0) return total_score # 碳排放总量 decline_patterns1 = [ r'碳排放总量[^,。]*?下降\s*([\d.]+)%', r'碳排放[^,。]*?总量[^,。]*?下降\s*([\d.]+)%', r'碳总量[^,。]*?下降\s*([\d.]+)%', r'排放总量[^,。]*?下降\s*([\d.]+)%', r'排放[^,。]*?下降\s*([\d.]+)%' ] total_score = cal_percent(decline_patterns1, content, data, 0, total_score) # 碳排放强度 decline_patterns2 = [ r'碳排放强度[^,。]*?下降\s*([\d.]+)%', r'碳强度[^,。]*?总量[^,。]*?下降\s*([\d.]+)%', r'排放强度[^,。]*?下降\s*([\d.]+)%' ] total_score = cal_percent(decline_patterns2, content, data, 1, total_score) # 产品碳足迹 decline_patterns3 = [ r'产品碳足迹[^,。]*?下降\s*([\d.]+)%', r'碳足迹[^,。]*?下降\s*([\d.]+)%', r'产品足迹[^,。]*?下降\s*([\d.]+)%' ] total_score = cal_percent(decline_patterns3, content, data, 2, total_score) total_score = round(total_score, 2) work.score_dq = total_score work.save(update_fields=["score_dq"]) return Response({"total_score": total_score, "data": data}) @staticmethod def parse_files(work: Work): contents = [] filenames = [] for file in [work.dh_file1, work.dh_file2, work.dh_file3, work.dh_file4, work.dh_file5, work.dh_file6]: if file: if file.name in filenames: continue path = (settings.BASE_DIR + file.path).replace('\\', '/') content = parse_file(path) filenames.append(file.name) contents.append(content) return '\n'.join(contents) @action(detail=True, methods=['post'], serializer_class=WorkDhCalSerializer) @transaction.atomic def cal_dh(self, request, pk): work = self.get_object() sr = WorkDhCalSerializer(work, data=request.data) sr.is_valid(raise_exception=True) sr.save() work = Work.objects.get(pk=pk) content = WorkViewSet.parse_files(work) fp_u = get_fingerprint(content) # unsigned fp_int = simhash_to_db(fp_u) # signed for db fp_hex = format(fp_u, "016x") s1, s2, s3, s4 = split_simhash(fp_int) # 1️⃣ 分段粗筛 candidates = ( Fingerprint.objects .filter( Q(seg1=s1) | Q(seg2=s2) | Q(seg3=s3) | Q(seg4=s4) ) .only("fp_int", "score") ) # 2️⃣ 精确海明距离 for obj in candidates: if hamming_distance(fp_u, obj.fp_int) <= HAMMING_THRESHOLD: work.score_dh = obj.score work.save(update_fields=["score_dh"]) return Response({"total_score": obj.score}) # 3️⃣ 未命中 → 调用 AI res = ask(content, "tec_dh") score = round(float(res), 2) work.score_dh = score work.save(update_fields=["score_dh"]) # 4️⃣ 并发安全写入指纹库 try: Fingerprint.objects.create( fp_hex=fp_hex, fp_int=fp_int, seg1=s1, seg2=s2, seg3=s3, seg4=s4, score=score, ) except IntegrityError: # 并发下已存在,忽略即可 pass return Response({"total_score": score})