bid-master-cli 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. app/__init__.py +1 -0
  2. app/api/__init__.py +1 -0
  3. app/api/api_keys.py +60 -0
  4. app/api/auth.py +258 -0
  5. app/api/cli_auth.py +165 -0
  6. app/api/database.py +286 -0
  7. app/api/extract.py +158 -0
  8. app/api/files.py +163 -0
  9. app/api/health.py +62 -0
  10. app/api/logs.py +26 -0
  11. app/api/settings.py +101 -0
  12. app/api/simulate.py +195 -0
  13. app/api/statistics.py +1214 -0
  14. app/cli.py +894 -0
  15. app/config.py +93 -0
  16. app/dependencies.py +12 -0
  17. app/infrastructure/__init__.py +1 -0
  18. app/infrastructure/database.py +126 -0
  19. app/infrastructure/db_schema.py +245 -0
  20. app/infrastructure/email_service.py +92 -0
  21. app/infrastructure/llm/__init__.py +1 -0
  22. app/infrastructure/llm/lite_llm.py +463 -0
  23. app/infrastructure/log_collector.py +64 -0
  24. app/infrastructure/mock_storage.py +563 -0
  25. app/infrastructure/pg_storage.py +656 -0
  26. app/infrastructure/storage.py +117 -0
  27. app/limiter.py +7 -0
  28. app/main.py +141 -0
  29. app/models/__init__.py +1 -0
  30. app/models/schemas.py +204 -0
  31. app/services/__init__.py +1 -0
  32. app/services/encryption_service.py +88 -0
  33. app/services/extract_service.py +817 -0
  34. app/services/file_service.py +112 -0
  35. app/services/llm_service.py +65 -0
  36. app/services/ocr_service.py +183 -0
  37. app/services/prompt_builder.py +257 -0
  38. app/services/simulate_service.py +625 -0
  39. app/services/statistics_service.py +123 -0
  40. app/utils/__init__.py +1 -0
  41. app/utils/auth_dep.py +42 -0
  42. app/utils/crypto.py +63 -0
  43. app/utils/exceptions.py +53 -0
  44. bid_master_cli-1.0.0.dist-info/METADATA +30 -0
  45. bid_master_cli-1.0.0.dist-info/RECORD +47 -0
  46. bid_master_cli-1.0.0.dist-info/WHEEL +4 -0
  47. bid_master_cli-1.0.0.dist-info/entry_points.txt +2 -0
app/api/settings.py ADDED
@@ -0,0 +1,101 @@
1
+ """
2
+ AI provider settings API routes.
3
+ """
4
+ from fastapi import APIRouter, HTTPException, Depends
5
+
6
+ from app.services.llm_service import LLMService
7
+ from app.models.schemas import (
8
+ ProvidersResponse,
9
+ TestConnectionRequest,
10
+ TestConnectionResponse,
11
+ ProviderConfig,
12
+ )
13
+ from app.utils.auth_dep import get_current_user
14
+
15
+ router = APIRouter(prefix="/settings", tags=["settings"])
16
+
17
+
18
+ @router.get("/providers")
19
+ async def get_providers():
20
+ """
21
+ Get list of supported AI providers.
22
+
23
+ Returns:
24
+ List of providers with their models
25
+ """
26
+ llm_service = LLMService()
27
+ providers = llm_service.get_providers()
28
+
29
+ return {
30
+ "success": True,
31
+ "data": {
32
+ "providers": providers,
33
+ "active": "deepseek", # Default provider
34
+ }
35
+ }
36
+
37
+
38
+ @router.get("/providers/{provider_name}")
39
+ async def get_provider(provider_name: str):
40
+ """
41
+ Get specific provider configuration.
42
+
43
+ Args:
44
+ provider_name: Provider identifier
45
+
46
+ Returns:
47
+ Provider details
48
+ """
49
+ llm_service = LLMService()
50
+ providers = llm_service.get_providers()
51
+
52
+ for p in providers:
53
+ if p["id"] == provider_name:
54
+ return {
55
+ "success": True,
56
+ "data": p,
57
+ }
58
+
59
+ raise HTTPException(status_code=404, detail=f"Provider {provider_name} not found")
60
+
61
+
62
+ @router.post("/providers/{provider_name}")
63
+ async def configure_provider(provider_name: str, config: ProviderConfig):
64
+ """
65
+ Configure a provider.
66
+
67
+ Args:
68
+ provider_name: Provider identifier
69
+ config: Provider configuration
70
+
71
+ Returns:
72
+ Success status
73
+ """
74
+ # TODO: Save to database
75
+ return {
76
+ "success": True,
77
+ "message": f"Provider {provider_name} configured",
78
+ }
79
+
80
+
81
+ @router.post("/test")
82
+ async def test_connection(request: TestConnectionRequest, user: dict = Depends(get_current_user)):
83
+ """Test connection to an AI provider."""
84
+ try:
85
+ llm_service = LLMService()
86
+ result = await llm_service.test_connection(
87
+ request.provider, model=request.model, user_id=user["id"], api_key=request.apiKey
88
+ )
89
+
90
+ return {
91
+ "success": result["success"],
92
+ "message": result["message"],
93
+ "latencyMs": result.get("latencyMs"),
94
+ "error": result.get("error"),
95
+ }
96
+ except Exception as e:
97
+ return {
98
+ "success": False,
99
+ "message": "Connection failed",
100
+ "error": str(e),
101
+ }
app/api/simulate.py ADDED
@@ -0,0 +1,195 @@
1
+ from __future__ import annotations
2
+ """
3
+ Simulate API routes - 4步模拟编制流程
4
+ """
5
+ import json
6
+
7
+ from fastapi import APIRouter, Depends, HTTPException
8
+ from pydantic import BaseModel
9
+ from typing import Optional, List
10
+ from sse_starlette.sse import EventSourceResponse
11
+
12
+ from app.services.simulate_service import SimulateService, get_simulate_service
13
+ from app.utils.auth_dep import get_current_user
14
+
15
+ router = APIRouter(prefix="/simulate", tags=["simulate"])
16
+
17
+
18
+ class CreateSimulateRequest(BaseModel):
19
+ file_ids: List[str]
20
+
21
+
22
+ class StepRequest(BaseModel):
23
+ provider: Optional[str] = "deepseek"
24
+ model: Optional[str] = None
25
+ params: Optional[dict] = None
26
+
27
+
28
+ simulate_service = get_simulate_service()
29
+
30
+
31
+ @router.post("/create")
32
+ async def create_simulate_task(request: CreateSimulateRequest, current_user: dict = Depends(get_current_user)):
33
+ """创建模拟任务(Step 0)"""
34
+ try:
35
+ task = await simulate_service.create_task(request.file_ids, user_id=current_user["id"])
36
+ return {
37
+ "success": True,
38
+ "data": {
39
+ "taskId": task.task_id,
40
+ "currentStep": task.current_step,
41
+ "status": task.status,
42
+ "fileIds": task.file_ids,
43
+ "sourceHash": task.source_hash,
44
+ }
45
+ }
46
+ except Exception as e:
47
+ raise HTTPException(status_code=500, detail=str(e))
48
+
49
+
50
+ @router.get("/list")
51
+ async def list_simulate_tasks(current_user: dict = Depends(get_current_user)):
52
+ """列出当前用户的模拟任务"""
53
+ tasks = simulate_service.list_tasks(user_id=current_user["id"])
54
+ return {
55
+ "success": True,
56
+ "data": {
57
+ "tasks": [
58
+ {
59
+ "taskId": t.task_id,
60
+ "currentStep": t.current_step,
61
+ "status": t.status,
62
+ "fileIds": t.file_ids,
63
+ "createdAt": t.created_at,
64
+ }
65
+ for t in tasks
66
+ ]
67
+ }
68
+ }
69
+
70
+
71
+ @router.get("/{task_id}")
72
+ async def get_simulate_task(task_id: str, current_user: dict = Depends(get_current_user)):
73
+ """获取任务状态"""
74
+ task = simulate_service.get_task(task_id, user_id=current_user["id"])
75
+ if not task:
76
+ raise HTTPException(status_code=404, detail="Task not found")
77
+
78
+ return {
79
+ "success": True,
80
+ "data": {
81
+ "taskId": task.task_id,
82
+ "currentStep": task.current_step,
83
+ "status": task.status,
84
+ "fileIds": task.file_ids,
85
+ "params": task.params,
86
+ "step1Result": task.step1_result,
87
+ "step2Result": task.step2_result,
88
+ "step3Result": task.step3_result,
89
+ "step4Result": task.step4_result,
90
+ "createdAt": task.created_at,
91
+ }
92
+ }
93
+
94
+
95
+ @router.delete("/{task_id}")
96
+ async def delete_simulate_task(task_id: str, current_user: dict = Depends(get_current_user)):
97
+ """删除任务"""
98
+ success = simulate_service.delete_task(task_id, user_id=current_user["id"])
99
+ return {"success": success}
100
+
101
+
102
+ @router.post("/{task_id}/step/1")
103
+ async def run_step1(task_id: str, current_user: dict = Depends(get_current_user)):
104
+ """Step 1: PDF转换"""
105
+ try:
106
+ task = await simulate_service.run_step1(task_id, user_id=current_user["id"])
107
+ return {
108
+ "success": True,
109
+ "data": {
110
+ "taskId": task.task_id,
111
+ "currentStep": task.current_step,
112
+ "status": task.status,
113
+ "step1Result": task.step1_result,
114
+ }
115
+ }
116
+ except ValueError as e:
117
+ raise HTTPException(status_code=404, detail=str(e))
118
+ except Exception as e:
119
+ raise HTTPException(status_code=500, detail=str(e))
120
+
121
+
122
+ async def stream_step2_generator(task_id: str, provider: str = "deepseek", user_id: str = None, model: str = None):
123
+ """Step 2 SSE generator"""
124
+ async for event in simulate_service.run_step2_stream(task_id, provider, user_id=user_id, model=model):
125
+ yield {"event": event.get("type", "message"), "data": json.dumps(event, ensure_ascii=False)}
126
+
127
+
128
+ @router.post("/{task_id}/step/2")
129
+ async def run_step2(task_id: str, request: StepRequest = None, current_user: dict = Depends(get_current_user)):
130
+ """Step 2: 提取(SSE流式)"""
131
+ try:
132
+ provider = request.provider if request else "deepseek"
133
+ model = request.model if request else None
134
+ return EventSourceResponse(stream_step2_generator(task_id, provider, user_id=current_user["id"], model=model))
135
+ except ValueError as e:
136
+ raise HTTPException(status_code=404, detail=str(e))
137
+ except Exception as e:
138
+ raise HTTPException(status_code=500, detail=str(e))
139
+
140
+
141
+ async def stream_step3_generator(task_id: str, provider: str = "deepseek", user_id: str = None, model: str = None):
142
+ """Step 3 SSE generator"""
143
+ async for event in simulate_service.run_step3_stream(task_id, provider, user_id=user_id, model=model):
144
+ yield {"event": event.get("type", "message"), "data": json.dumps(event, ensure_ascii=False)}
145
+
146
+
147
+ @router.post("/{task_id}/step/3")
148
+ async def run_step3(task_id: str, request: StepRequest = None, current_user: dict = Depends(get_current_user)):
149
+ """Step 3: 对比(SSE流式)"""
150
+ try:
151
+ provider = request.provider if request else "deepseek"
152
+ model = request.model if request else None
153
+ return EventSourceResponse(stream_step3_generator(task_id, provider, user_id=current_user["id"], model=model))
154
+ except ValueError as e:
155
+ raise HTTPException(status_code=404, detail=str(e))
156
+ except Exception as e:
157
+ raise HTTPException(status_code=500, detail=str(e))
158
+
159
+
160
+ async def stream_step4_generator(task_id: str, params: dict, provider: str = "deepseek", user_id: str = None, model: str = None):
161
+ """Step 4 SSE generator"""
162
+ async for event in simulate_service.run_step4_stream(task_id, params, provider, user_id=user_id, model=model):
163
+ yield {"event": event.get("type", "message"), "data": json.dumps(event, ensure_ascii=False)}
164
+
165
+
166
+ class Step4Request(BaseModel):
167
+ provider: Optional[str] = "deepseek"
168
+ model: Optional[str] = None
169
+ project_name: str
170
+ project_type: str
171
+ project_scale: str
172
+ investment_estimate: Optional[str] = ""
173
+ region: Optional[str] = ""
174
+ special_requirements: Optional[str] = ""
175
+
176
+
177
+ @router.post("/{task_id}/step/4")
178
+ async def run_step4(task_id: str, request: Step4Request, current_user: dict = Depends(get_current_user)):
179
+ """Step 4: 编制(SSE流式)"""
180
+ try:
181
+ params = {
182
+ "project_name": request.project_name,
183
+ "project_type": request.project_type,
184
+ "project_scale": request.project_scale,
185
+ "investment_estimate": request.investment_estimate,
186
+ "region": request.region,
187
+ "special_requirements": request.special_requirements,
188
+ }
189
+ return EventSourceResponse(
190
+ stream_step4_generator(task_id, params, request.provider, user_id=current_user["id"], model=request.model)
191
+ )
192
+ except ValueError as e:
193
+ raise HTTPException(status_code=404, detail=str(e))
194
+ except Exception as e:
195
+ raise HTTPException(status_code=500, detail=str(e))