import os import json import logging import sqlite3 import requests import datetime from flask import Flask, render_template, request, jsonify, send_from_directory from werkzeug.utils import secure_filename # Configuration BASE_DIR = os.path.dirname(os.path.abspath(__file__)) INSTANCE_PATH = os.path.join(BASE_DIR, "instance") DB_PATH = os.path.join(INSTANCE_PATH, "permit_flow.db") os.makedirs(INSTANCE_PATH, exist_ok=True) app = Flask(__name__, instance_path=INSTANCE_PATH) app.config['SECRET_KEY'] = 'dev-secret-key-permit-flow' app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max upload # Logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # SiliconFlow API Configuration SILICONFLOW_API_KEY = "sk-vimuseiptfbomzegyuvmebjzooncsqbyjtlddrfodzcdskgi" SILICONFLOW_API_URL = "https://api.siliconflow.cn/v1/chat/completions" MODEL_NAME = "Qwen/Qwen2.5-7B-Instruct" # Database Setup def get_db(): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row return conn def init_db(): conn = get_db() c = conn.cursor() # Projects table (e.g., "Open a Cafe") c.execute('''CREATE TABLE IF NOT EXISTS projects ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT, industry TEXT, location TEXT, status TEXT DEFAULT 'planning', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, risk_score INTEGER DEFAULT 0 )''') # Permits/Licenses table c.execute('''CREATE TABLE IF NOT EXISTS permits ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_id INTEGER, name TEXT NOT NULL, authority TEXT, status TEXT DEFAULT 'pending', -- pending, preparing, submitted, approved priority TEXT DEFAULT 'medium', requirements TEXT, -- JSON list of requirements estimated_time TEXT, FOREIGN KEY (project_id) REFERENCES projects (id) )''') # Assets table (generated docs, uploaded files) c.execute('''CREATE TABLE IF NOT EXISTS assets ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_id INTEGER, permit_id INTEGER, name TEXT NOT NULL, type TEXT, -- document, form, guide content TEXT, -- Markdown content or file path created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (project_id) REFERENCES projects (id) )''') conn.commit() conn.close() # Initialize DB on start init_db() def populate_default_data(): conn = get_db() cursor = conn.cursor() # Check if projects exist cursor.execute('SELECT count(*) FROM projects') if cursor.fetchone()[0] == 0: logger.info("Populating default data...") # Create a default project default_project = { "name": "示例:成都市高新区精品咖啡馆", "description": "计划在成都市高新区天府三街开设一家主要经营精品手冲咖啡和少量西式甜点的咖啡馆,面积约80平方米。", "industry": "餐饮/食品", "location": "成都市高新区", "risk_score": 45 } cursor.execute('INSERT INTO projects (name, description, industry, location, risk_score) VALUES (?, ?, ?, ?, ?)', (default_project['name'], default_project['description'], default_project['industry'], default_project['location'], default_project['risk_score'])) project_id = cursor.lastrowid # Default permits permits = [ {"name": "营业执照", "authority": "市场监督管理局", "priority": "high", "estimated_time": "3个工作日", "requirements": ["身份证原件", "经营场所证明", "名称预先核准通知书"]}, {"name": "食品经营许可证", "authority": "市场监督管理局", "priority": "high", "estimated_time": "15个工作日", "requirements": ["营业执照", "健康证", "食品安全规章制度", "平面布局图"]}, {"name": "招牌设置许可", "authority": "城市管理行政执法局", "priority": "medium", "estimated_time": "5个工作日", "requirements": ["效果图", "租赁合同", "营业执照复印件"]} ] for p in permits: reqs_json = json.dumps(p['requirements'], ensure_ascii=False) cursor.execute('INSERT INTO permits (project_id, name, authority, priority, estimated_time, requirements) VALUES (?, ?, ?, ?, ?, ?)', (project_id, p['name'], p['authority'], p['priority'], p['estimated_time'], reqs_json)) permit_id = cursor.lastrowid # Add a sample asset for the first permit if p['name'] == "食品经营许可证": content = "# 食品安全管理制度\n\n## 第一章 总则\n\n第一条 为保障食品安全,根据《食品安全法》制定本制度。\n\n## 第二章 从业人员健康管理\n\n第二条 从业人员必须持有效健康证上岗..." cursor.execute('INSERT INTO assets (project_id, permit_id, name, type, content) VALUES (?, ?, ?, ?, ?)', (project_id, permit_id, "食品安全管理制度范本", "document", content)) conn.commit() conn.close() populate_default_data() # --- AI Service --- def call_ai_service(system_prompt, user_prompt, temperature=0.7): headers = { "Authorization": f"Bearer {SILICONFLOW_API_KEY}", "Content-Type": "application/json" } payload = { "model": MODEL_NAME, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], "temperature": temperature, "max_tokens": 2048 } try: response = requests.post(SILICONFLOW_API_URL, headers=headers, json=payload, timeout=30) response.raise_for_status() result = response.json() content = result['choices'][0]['message']['content'] return content except Exception as e: logger.error(f"AI Service Error: {str(e)}") # Mock Fallback return None def analyze_business_scenario(name, description, location): system_prompt = """ 你是一个专业的商业合规与行政审批专家智能体 (Permit Flow Agent)。 你的任务是根据用户的商业计划,分析需要办理的行政许可证照。 请以 JSON 格式输出分析结果,不要包含 markdown 代码块标记,直接返回 JSON 对象。 JSON 结构如下: { "analysis": "对商业计划的简短合规性分析", "risk_score": 0-100 (风险评分,越高越难), "permits": [ { "name": "许可证名称 (如:食品经营许可证)", "authority": "审批部门 (如:市场监督管理局)", "priority": "high/medium/low", "estimated_time": "预计耗时 (如:15个工作日)", "requirements": ["要求1", "要求2"] } ], "suggested_assets": [ { "name": "资产名称 (如:食品安全管理制度范本)", "type": "document", "content_brief": "文档的大致内容描述" } ] } """ user_prompt = f"我的商业计划是:{name}。\n详细描述:{description}。\n所在地:{location}。\n请帮我分析需要办理哪些证照。" ai_response = call_ai_service(system_prompt, user_prompt) if ai_response: try: # Clean up markdown code blocks if present cleaned_response = ai_response.replace("```json", "").replace("```", "").strip() return json.loads(cleaned_response) except json.JSONDecodeError: logger.error("Failed to parse AI JSON response") pass # Fallthrough to mock # Mock Data Fallback logger.warning("Using Mock Data for Analysis") return { "analysis": "模拟模式:基于您的描述,这是一个标准的商业实体注册流程。系统已为您生成预设的合规路径。", "risk_score": 35, "permits": [ { "name": "营业执照", "authority": "市场监督管理局", "priority": "high", "estimated_time": "3个工作日", "requirements": ["身份证原件", "场地证明", "核名通知书"] }, { "name": "行业综合许可证", "authority": "行政审批局", "priority": "medium", "estimated_time": "10个工作日", "requirements": ["申请表", "平面图", "承诺书"] } ], "suggested_assets": [ { "name": "企业设立登记申请书", "type": "form", "content_brief": "标准申请表格模板" } ] } def generate_asset_content(asset_name, context): system_prompt = """ 你是一个专业的商业文书撰写助手。请根据上下文生成详细的文档内容。 输出格式为 Markdown。 """ user_prompt = f"请为项目撰写一份'{asset_name}'。\n背景信息:{context}" content = call_ai_service(system_prompt, user_prompt) if not content: return f"# {asset_name}\n\n*(模拟模式生成内容)*\n\n这是系统自动生成的{asset_name}模板。\n\n1. 请填写相关信息...\n2. 签字盖章..." return content # --- Routes --- @app.route('/') def index(): return render_template('index.html') @app.route('/api/projects', methods=['GET']) def list_projects(): conn = get_db() projects = conn.execute('SELECT * FROM projects ORDER BY created_at DESC').fetchall() conn.close() return jsonify([dict(p) for p in projects]) @app.route('/api/projects', methods=['POST']) def create_project(): data = request.json name = data.get('name') description = data.get('description') location = data.get('location', 'Unknown') industry = data.get('industry', 'General') # 1. AI Analysis analysis = analyze_business_scenario(name, description, location) conn = get_db() cursor = conn.cursor() # 2. Save Project cursor.execute('INSERT INTO projects (name, description, industry, location, risk_score) VALUES (?, ?, ?, ?, ?)', (name, analysis['analysis'], industry, location, analysis['risk_score'])) project_id = cursor.lastrowid # 3. Save Permits for permit in analysis['permits']: reqs_json = json.dumps(permit['requirements'], ensure_ascii=False) cursor.execute('INSERT INTO permits (project_id, name, authority, priority, estimated_time, requirements) VALUES (?, ?, ?, ?, ?, ?)', (project_id, permit['name'], permit['authority'], permit['priority'], permit['estimated_time'], reqs_json)) permit_id = cursor.lastrowid # Create initial asset for this permit if available # (Simplified: just create one general asset for now based on suggestions) # 4. Create Suggested Assets for asset in analysis.get('suggested_assets', []): # Generate content immediately or later? Let's generate a placeholder first to be fast content = f"# {asset['name']}\n\n等待生成..." cursor.execute('INSERT INTO assets (project_id, permit_id, name, type, content) VALUES (?, ?, ?, ?, ?)', (project_id, 0, asset['name'], asset['type'], content)) conn.commit() conn.close() return jsonify({"success": True, "project_id": project_id}) @app.route('/api/projects/', methods=['GET']) def get_project(project_id): conn = get_db() project = conn.execute('SELECT * FROM projects WHERE id = ?', (project_id,)).fetchone() permits = conn.execute('SELECT * FROM permits WHERE project_id = ?', (project_id,)).fetchall() assets = conn.execute('SELECT * FROM assets WHERE project_id = ?', (project_id,)).fetchall() conn.close() if not project: return jsonify({"error": "Not found"}), 404 return jsonify({ "project": dict(project), "permits": [dict(p) for p in permits], "assets": [dict(a) for a in assets] }) @app.route('/api/upload', methods=['POST']) def upload_file(): if 'file' not in request.files: return jsonify({"error": "No file part"}), 400 file = request.files['file'] if file.filename == '': return jsonify({"error": "No selected file"}), 400 if file: filename = secure_filename(file.filename) # In a real app, save to disk or S3. Here we just return success and mock a DB entry # save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) # file.save(save_path) # Add to assets table as a record project_id = request.form.get('project_id') if project_id: conn = get_db() cursor = conn.cursor() cursor.execute('INSERT INTO assets (project_id, permit_id, name, type, content) VALUES (?, ?, ?, ?, ?)', (project_id, 0, filename, 'file', f"已上传文件: {filename} (存储于服务器)")) conn.commit() conn.close() return jsonify({"success": True, "filename": filename}) return jsonify({"error": "Upload failed"}), 500 @app.errorhandler(404) def page_not_found(e): return render_template('index.html'), 200 # Support SPA routing if needed, or just return index @app.errorhandler(500) def internal_server_error(e): return jsonify(error=str(e)), 500 @app.route('/api/assets/generate', methods=['POST']) def generate_asset(): data = request.json asset_id = data.get('asset_id') project_context = data.get('context', '') conn = get_db() asset = conn.execute('SELECT * FROM assets WHERE id = ?', (asset_id,)).fetchone() if not asset: conn.close() return jsonify({"error": "Asset not found"}), 404 content = generate_asset_content(asset['name'], project_context) conn.execute('UPDATE assets SET content = ? WHERE id = ?', (content, asset_id)) conn.commit() conn.close() return jsonify({"success": True, "content": content}) @app.route('/api/permits//status', methods=['POST']) def update_permit_status(permit_id): data = request.json status = data.get('status') conn = get_db() conn.execute('UPDATE permits SET status = ? WHERE id = ?', (status, permit_id)) conn.commit() conn.close() return jsonify({"success": True}) if __name__ == '__main__': app.run(host='0.0.0.0', port=7860, debug=True)