File size: 15,003 Bytes
0f22fe9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
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/<int:project_id>', 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/<int:permit_id>/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)