galaxy-opc-plugin 0.2.0 → 0.2.2

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 (46) hide show
  1. package/index.ts +244 -8
  2. package/package.json +17 -3
  3. package/skills/acquisition-management/SKILL.md +83 -0
  4. package/skills/ai-staff/SKILL.md +89 -0
  5. package/skills/asset-package/SKILL.md +142 -0
  6. package/skills/opb-canvas/SKILL.md +88 -0
  7. package/src/__tests__/e2e/company-lifecycle.test.ts +399 -0
  8. package/src/__tests__/integration/business-workflows.test.ts +366 -0
  9. package/src/__tests__/test-utils.ts +316 -0
  10. package/src/commands/opc-command.ts +422 -0
  11. package/src/db/index.ts +3 -0
  12. package/src/db/migrations.test.ts +324 -0
  13. package/src/db/migrations.ts +131 -0
  14. package/src/db/schema.ts +211 -0
  15. package/src/db/sqlite-adapter.ts +5 -0
  16. package/src/opc/autonomy-rules.ts +132 -0
  17. package/src/opc/briefing-builder.ts +1331 -0
  18. package/src/opc/business-workflows.test.ts +535 -0
  19. package/src/opc/business-workflows.ts +325 -0
  20. package/src/opc/context-injector.ts +366 -28
  21. package/src/opc/event-triggers.ts +472 -0
  22. package/src/opc/intelligence-engine.ts +702 -0
  23. package/src/opc/milestone-detector.ts +251 -0
  24. package/src/opc/proactive-service.ts +179 -0
  25. package/src/opc/reminder-service.ts +4 -43
  26. package/src/opc/session-task-tracker.ts +60 -0
  27. package/src/opc/stage-detector.ts +168 -0
  28. package/src/opc/task-executor.ts +332 -0
  29. package/src/opc/task-templates.ts +179 -0
  30. package/src/tools/acquisition-tool.ts +8 -5
  31. package/src/tools/document-tool.ts +1176 -0
  32. package/src/tools/finance-tool.test.ts +238 -0
  33. package/src/tools/finance-tool.ts +922 -14
  34. package/src/tools/hr-tool.ts +10 -1
  35. package/src/tools/legal-tool.test.ts +251 -0
  36. package/src/tools/legal-tool.ts +26 -4
  37. package/src/tools/lifecycle-tool.test.ts +231 -0
  38. package/src/tools/media-tool.ts +156 -1
  39. package/src/tools/monitoring-tool.ts +135 -2
  40. package/src/tools/opc-tool.test.ts +250 -0
  41. package/src/tools/opc-tool.ts +251 -28
  42. package/src/tools/project-tool.test.ts +218 -0
  43. package/src/tools/schemas.ts +80 -0
  44. package/src/tools/search-tool.ts +227 -0
  45. package/src/tools/staff-tool.ts +395 -2
  46. package/src/web/config-ui.ts +299 -45
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: opb-canvas
3
+ description: |
4
+ OPB 商业画布技能。当用户提到商业画布、OPB画布、商业模式、一人企业方法论、战略规划、商业设计时激活。
5
+ ---
6
+
7
+ # OPB 商业画布技能
8
+
9
+ 使用 `opc_opb` 工具基于《一人企业方法论 2.0》创建和维护 16 模块的 OPB Canvas,帮助创始人系统化设计一人公司的战略蓝图。
10
+
11
+ ## 画布 16 模块
12
+
13
+ | 模块 | 字段名 | 核心问题 |
14
+ |------|--------|---------|
15
+ | 赛道 | track | 你在哪个行业/细分市场? |
16
+ | 目标客户 | target_customer | 谁是你的理想客户? |
17
+ | 核心痛点 | pain_point | 客户最大的痛点是什么? |
18
+ | 解决方案 | solution | 你如何解决这个痛点? |
19
+ | 独特价值 | unique_value | 你的 USP(独特卖点)是什么? |
20
+ | 获客渠道 | channels | 怎样获得客户? |
21
+ | 收入模式 | revenue_model | 怎样赚钱? |
22
+ | 成本结构 | cost_structure | 主要成本有哪些? |
23
+ | 关键资源 | key_resources | 需要什么核心资源? |
24
+ | 关键活动 | key_activities | 每天做什么关键动作? |
25
+ | 关键伙伴 | key_partners | 需要哪些合作伙伴? |
26
+ | 不公平优势 | unfair_advantage | 别人无法轻易复制的优势? |
27
+ | 关键指标 | metrics | 用什么指标衡量成功? |
28
+ | 非竞争策略 | non_compete | 如何避免正面竞争? |
29
+ | 规模化路径 | scaling_strategy | 如何从一人扩展到更大? |
30
+ | 备注 | notes | 其他补充信息 |
31
+
32
+ ## 使用流程
33
+
34
+ ### 第一步:初始化画布
35
+
36
+ ```json
37
+ {
38
+ "action": "canvas_init",
39
+ "company_id": "公司ID"
40
+ }
41
+ ```
42
+
43
+ 每家公司只能有一个画布,重复初始化会提示已存在。
44
+
45
+ ### 第二步:逐步填写
46
+
47
+ 引导用户从赛道开始,逐一思考每个模块:
48
+
49
+ ```json
50
+ {
51
+ "action": "canvas_update",
52
+ "company_id": "公司ID",
53
+ "track": "AI 应用开发",
54
+ "target_customer": "中小企业主,需要 AI 自动化但缺乏技术团队",
55
+ "pain_point": "AI 技术门槛高,定制开发费用昂贵",
56
+ "solution": "提供开箱即用的 AI 自动化工具包"
57
+ }
58
+ ```
59
+
60
+ 可以一次更新多个字段,也可以分多次逐步完善。
61
+
62
+ ### 第三步:查看画布和完成度
63
+
64
+ ```json
65
+ {
66
+ "action": "canvas_get",
67
+ "company_id": "公司ID"
68
+ }
69
+ ```
70
+
71
+ 返回所有模块内容及完成百分比(已填写模块数 / 16)。
72
+
73
+ ## 引导对话建议
74
+
75
+ 作为 AI 助手,可以用以下方式引导用户填写画布:
76
+
77
+ 1. **赛道选择**: "你擅长什么?市场有什么机会?两者交集在哪里?"
78
+ 2. **客户定义**: "描述你最理想的客户,越具体越好——他们的身份、痛点、预算"
79
+ 3. **独特价值**: "如果客户只能记住你一句话,那句话是什么?"
80
+ 4. **收入模式**: "一次性收费、订阅制、还是按效果付费?"
81
+ 5. **不公平优势**: "你有什么是竞争对手短期内无法获得的?(经验、人脉、技术、数据)"
82
+
83
+ ## 使用建议
84
+
85
+ 1. 注册公司后尽早初始化画布,帮助理清商业思路
86
+ 2. 不必一次填完所有模块,可以分多次迭代
87
+ 3. 每月回顾一次画布,根据实际运营调整
88
+ 4. 画布完成度低于 50% 时,重点引导用户补充核心模块(赛道、客户、痛点、方案、收入模式)
@@ -0,0 +1,399 @@
1
+ /**
2
+ * 星环OPC中心 — 端到端测试:完整公司生命周期
3
+ *
4
+ * 测试从公司注册到盈利的完整业务流程
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
8
+ import { createTestDb, factories } from "../test-utils.js";
9
+ import { SqliteAdapter } from "../../db/sqlite-adapter.js";
10
+ import { BusinessWorkflows } from "../../opc/business-workflows.js";
11
+
12
+ describe("company lifecycle E2E", () => {
13
+ let db: SqliteAdapter;
14
+ let workflows: BusinessWorkflows;
15
+
16
+ beforeEach(() => {
17
+ db = createTestDb();
18
+ workflows = new BusinessWorkflows(db);
19
+ });
20
+
21
+ afterEach(() => {
22
+ db.close();
23
+ });
24
+
25
+ it("complete journey: from registration to profitability", () => {
26
+ // ═══════════════════════════════════════════════════════════
27
+ // 第一步:注册公司
28
+ // ═══════════════════════════════════════════════════════════
29
+ const company = db.createCompany({
30
+ name: "张三的咨询公司",
31
+ industry: "咨询",
32
+ owner_name: "张三",
33
+ owner_contact: "13800138000",
34
+ status: "active",
35
+ registered_capital: 100000,
36
+ description: "专业提供商业咨询服务",
37
+ });
38
+
39
+ expect(company).not.toBeNull();
40
+ expect(company.id).toBeDefined();
41
+ expect(company.name).toBe("张三的咨询公司");
42
+
43
+ // ═══════════════════════════════════════════════════════════
44
+ // 第二步:添加创始人为员工
45
+ // ═══════════════════════════════════════════════════════════
46
+ const employeeId = db.genId();
47
+ db.execute(
48
+ `INSERT INTO opc_employees (id, company_id, name, role, skills, status, created_at)
49
+ VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`,
50
+ employeeId, company.id, "张三", "general", "商业咨询,战略规划", "active"
51
+ );
52
+
53
+ const employee = db.queryOne(
54
+ "SELECT * FROM opc_employees WHERE id = ?",
55
+ employeeId
56
+ ) as any;
57
+ expect(employee.name).toBe("张三");
58
+
59
+ // ═══════════════════════════════════════════════════════════
60
+ // 第三步:签订首个服务合同
61
+ // ═══════════════════════════════════════════════════════════
62
+ const contractId = db.genId();
63
+ const now = new Date().toISOString();
64
+
65
+ db.execute(
66
+ `INSERT INTO opc_contracts
67
+ (id, company_id, title, counterparty, contract_type, amount, start_date, end_date, status, key_terms, risk_notes, reminder_date, created_at, updated_at)
68
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
69
+ contractId, company.id, "商业咨询服务合同", "创业公司A", "service", 100000,
70
+ "2026-01-01", "2026-06-30", "active",
71
+ "按月交付报告,分3期付款",
72
+ "",
73
+ "2026-06-15",
74
+ now, now
75
+ );
76
+
77
+ // 触发业务工作流(自动创建联系人等)
78
+ const contractResults = workflows.afterContractCreated({
79
+ id: contractId,
80
+ company_id: company.id,
81
+ title: "商业咨询服务合同",
82
+ counterparty: "创业公司A",
83
+ contract_type: "service",
84
+ direction: "sales",
85
+ amount: 100000,
86
+ start_date: "2026-01-01",
87
+ end_date: "2026-06-30",
88
+ });
89
+
90
+ expect(contractResults.length).toBeGreaterThan(0);
91
+
92
+ // 验证自动创建了客户联系人
93
+ const contact = db.queryOne(
94
+ "SELECT * FROM opc_contacts WHERE company_id = ? AND name = ?",
95
+ company.id, "创业公司A"
96
+ ) as any;
97
+ expect(contact).not.toBeNull();
98
+ expect(contact.tags).toContain("客户");
99
+
100
+ // ═══════════════════════════════════════════════════════════
101
+ // 第四步:记录收款(3期付款)
102
+ // ═══════════════════════════════════════════════════════════
103
+ const payments = [
104
+ { date: "2026-02-15", amount: 30000, desc: "第一期款" },
105
+ { date: "2026-04-15", amount: 30000, desc: "第二期款" },
106
+ { date: "2026-06-15", amount: 40000, desc: "第三期款(尾款)" },
107
+ ];
108
+
109
+ payments.forEach((payment) => {
110
+ const txId = db.genId();
111
+ db.execute(
112
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
113
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
114
+ txId, company.id, "income", "service_income", payment.amount,
115
+ payment.desc, "创业公司A", payment.date
116
+ );
117
+
118
+ // 触发交易工作流
119
+ workflows.afterTransactionCreated({
120
+ id: txId,
121
+ company_id: company.id,
122
+ type: "income",
123
+ amount: payment.amount,
124
+ counterparty: "创业公司A",
125
+ description: payment.desc,
126
+ });
127
+ });
128
+
129
+ // 验证总收入
130
+ const incomeSummary = db.queryOne(
131
+ `SELECT SUM(amount) as total_income
132
+ FROM opc_transactions
133
+ WHERE company_id = ? AND type = 'income'`,
134
+ company.id
135
+ ) as any;
136
+ expect(incomeSummary.total_income).toBe(100000);
137
+
138
+ // ═══════════════════════════════════════════════════════════
139
+ // 第五步:记录运营成本
140
+ // ═══════════════════════════════════════════════════════════
141
+ const expenses = [
142
+ { date: "2026-01-25", amount: 5000, category: "rent", desc: "办公室租金" },
143
+ { date: "2026-02-10", amount: 2000, category: "utilities", desc: "水电网费" },
144
+ { date: "2026-03-15", amount: 3000, category: "marketing", desc: "市场推广" },
145
+ { date: "2026-04-20", amount: 1500, category: "supplies", desc: "办公用品" },
146
+ ];
147
+
148
+ expenses.forEach((expense) => {
149
+ const txId = db.genId();
150
+ db.execute(
151
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
152
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
153
+ txId, company.id, "expense", expense.category, expense.amount,
154
+ expense.desc, "供应商", expense.date
155
+ );
156
+ });
157
+
158
+ // 验证总支出
159
+ const expenseSummary = db.queryOne(
160
+ `SELECT SUM(amount) as total_expense
161
+ FROM opc_transactions
162
+ WHERE company_id = ? AND type = 'expense'`,
163
+ company.id
164
+ ) as any;
165
+ expect(expenseSummary.total_expense).toBe(11500);
166
+
167
+ // ═══════════════════════════════════════════════════════════
168
+ // 第六步:验证财务状态(盈利)
169
+ // ═══════════════════════════════════════════════════════════
170
+ const financialSummary = db.queryOne(
171
+ `SELECT
172
+ COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) as total_revenue,
173
+ COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) as total_cost
174
+ FROM opc_transactions WHERE company_id = ?`,
175
+ company.id
176
+ ) as any;
177
+
178
+ const profit = financialSummary.total_revenue - financialSummary.total_cost;
179
+
180
+ expect(financialSummary.total_revenue).toBe(100000);
181
+ expect(financialSummary.total_cost).toBe(11500);
182
+ expect(profit).toBe(88500);
183
+ expect(profit).toBeGreaterThan(0); // 实现盈利
184
+
185
+ // ═══════════════════════════════════════════════════════════
186
+ // 第七步:验证里程碑记录
187
+ // ═══════════════════════════════════════════════════════════
188
+ const milestones = db.query(
189
+ "SELECT * FROM opc_milestones WHERE company_id = ?",
190
+ company.id
191
+ ) as any[];
192
+
193
+ // 检查里程碑是否存在 (业务工作流可能会创建)
194
+ // Schema uses: title, category instead of milestone_type
195
+ expect(milestones.length).toBeGreaterThanOrEqual(0);
196
+
197
+ // ═══════════════════════════════════════════════════════════
198
+ // 第八步:验证公司健康度指标
199
+ // ═══════════════════════════════════════════════════════════
200
+ const healthMetrics = {
201
+ // 1. 客户数量(检查是否有联系人被创建)
202
+ customerCount: (db.query(
203
+ "SELECT COUNT(*) as count FROM opc_contacts WHERE company_id = ?",
204
+ company.id
205
+ ) as any[])[0].count,
206
+
207
+ // 2. 活跃合同数
208
+ activeContracts: (db.query(
209
+ "SELECT COUNT(*) as count FROM opc_contracts WHERE company_id = ? AND status = 'active'",
210
+ company.id
211
+ ) as any[])[0].count,
212
+
213
+ // 3. 收入流水笔数
214
+ transactionCount: (db.query(
215
+ "SELECT COUNT(*) as count FROM opc_transactions WHERE company_id = ? AND type = 'income'",
216
+ company.id
217
+ ) as any[])[0].count,
218
+
219
+ // 4. 利润率
220
+ profitMargin: (profit / financialSummary.total_revenue) * 100,
221
+ };
222
+
223
+ expect(healthMetrics.customerCount).toBeGreaterThan(0);
224
+ expect(healthMetrics.activeContracts).toBeGreaterThan(0);
225
+ expect(healthMetrics.transactionCount).toBeGreaterThanOrEqual(3);
226
+ expect(healthMetrics.profitMargin).toBeGreaterThan(80); // 88.5%
227
+
228
+ // ═══════════════════════════════════════════════════════════
229
+ // 第九步:公司状态检查
230
+ // ═══════════════════════════════════════════════════════════
231
+ const finalCompany = db.getCompany(company.id);
232
+ expect(finalCompany).not.toBeNull();
233
+ expect(finalCompany!.status).toBe("active");
234
+ expect(finalCompany!.name).toBe("张三的咨询公司");
235
+
236
+ // ═══════════════════════════════════════════════════════════
237
+ // 测试总结:成功模拟了一个一人公司从注册到盈利的完整生命周期
238
+ // ═══════════════════════════════════════════════════════════
239
+ const summary = {
240
+ company: finalCompany!.name,
241
+ status: finalCompany!.status,
242
+ revenue: financialSummary.total_revenue,
243
+ cost: financialSummary.total_cost,
244
+ profit,
245
+ profitMargin: `${healthMetrics.profitMargin.toFixed(2)}%`,
246
+ customers: healthMetrics.customerCount,
247
+ contracts: healthMetrics.activeContracts,
248
+ milestones: milestones.length,
249
+ };
250
+
251
+ // 验证公司已经成功运营
252
+ expect(summary.profit).toBeGreaterThan(0);
253
+ expect(summary.customers).toBeGreaterThan(0);
254
+ expect(summary.contracts).toBeGreaterThan(0);
255
+
256
+ console.log("✓ E2E Test Summary:", summary);
257
+ });
258
+
259
+ it("multi-contract scenario: expanding business", () => {
260
+ // ═══════════════════════════════════════════════════════════
261
+ // 场景:公司接连签订多个合同,业务扩张
262
+ // ═══════════════════════════════════════════════════════════
263
+ const company = db.createCompany(factories.company({
264
+ name: "快速成长科技公司",
265
+ industry: "科技",
266
+ }));
267
+
268
+ // 签订3个不同客户的合同
269
+ const clients = ["客户A", "客户B", "客户C"];
270
+ const now = new Date().toISOString();
271
+
272
+ clients.forEach((client, index) => {
273
+ const contractId = db.genId();
274
+ const amount = (index + 1) * 50000;
275
+
276
+ db.execute(
277
+ `INSERT INTO opc_contracts
278
+ (id, company_id, title, counterparty, contract_type, amount, start_date, end_date, status, key_terms, risk_notes, reminder_date, created_at, updated_at)
279
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
280
+ contractId, company.id, `${client}服务合同`, client, "service", amount,
281
+ "2026-01-01", "2026-12-31", "active", "", "", "2026-11-30", now, now
282
+ );
283
+
284
+ workflows.afterContractCreated({
285
+ id: contractId,
286
+ company_id: company.id,
287
+ title: `${client}服务合同`,
288
+ counterparty: client,
289
+ contract_type: "service",
290
+ direction: "sales",
291
+ amount,
292
+ start_date: "2026-01-01",
293
+ end_date: "2026-12-31",
294
+ });
295
+
296
+ // 记录收款
297
+ const txId = db.genId();
298
+ db.execute(
299
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
300
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
301
+ txId, company.id, "income", "service_income", amount,
302
+ `${client}项目收款`, client, "2026-02-15"
303
+ );
304
+ });
305
+
306
+ // 验证:应该有3个客户
307
+ const customers = db.query(
308
+ "SELECT * FROM opc_contacts WHERE company_id = ? AND tags LIKE '%客户%'",
309
+ company.id
310
+ ) as any[];
311
+ expect(customers.length).toBe(3);
312
+
313
+ // 验证:应该有3个活跃合同
314
+ const contracts = db.query(
315
+ "SELECT * FROM opc_contracts WHERE company_id = ? AND status = 'active'",
316
+ company.id
317
+ ) as any[];
318
+ expect(contracts.length).toBe(3);
319
+
320
+ // 验证:总收入应该是 50000 + 100000 + 150000 = 300000
321
+ const totalRevenue = db.queryOne(
322
+ `SELECT SUM(amount) as total
323
+ FROM opc_transactions
324
+ WHERE company_id = ? AND type = 'income'`,
325
+ company.id
326
+ ) as any;
327
+ expect(totalRevenue.total).toBe(300000);
328
+ });
329
+
330
+ it("failure scenario: company with losses", () => {
331
+ // ═══════════════════════════════════════════════════════════
332
+ // 场景:公司亏损,支出大于收入
333
+ // ═══════════════════════════════════════════════════════════
334
+ const company = db.createCompany(factories.company({
335
+ name: "亏损公司案例",
336
+ industry: "零售",
337
+ }));
338
+
339
+ // 收入较少
340
+ const incomeId = db.genId();
341
+ db.execute(
342
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
343
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
344
+ incomeId, company.id, "income", "product_income", 30000,
345
+ "销售收入", "客户", "2026-01-15"
346
+ );
347
+
348
+ // 支出较多
349
+ const expenses = [
350
+ { amount: 20000, category: "rent", desc: "租金" },
351
+ { amount: 15000, category: "salary", desc: "工资" },
352
+ { amount: 10000, category: "marketing", desc: "推广" },
353
+ ];
354
+
355
+ expenses.forEach((expense) => {
356
+ const txId = db.genId();
357
+ db.execute(
358
+ `INSERT INTO opc_transactions (id, company_id, type, category, amount, description, counterparty, transaction_date, created_at)
359
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
360
+ txId, company.id, "expense", expense.category, expense.amount,
361
+ expense.desc, "供应商", "2026-01-20"
362
+ );
363
+ });
364
+
365
+ // 计算盈亏
366
+ const summary = db.queryOne(
367
+ `SELECT
368
+ COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) as revenue,
369
+ COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) as cost
370
+ FROM opc_transactions WHERE company_id = ?`,
371
+ company.id
372
+ ) as any;
373
+
374
+ const profit = summary.revenue - summary.cost;
375
+
376
+ expect(summary.revenue).toBe(30000);
377
+ expect(summary.cost).toBe(45000);
378
+ expect(profit).toBe(-15000);
379
+ expect(profit).toBeLessThan(0); // 确认亏损
380
+
381
+ // 这种情况下,公司可能需要进入收购流程
382
+ // 可以创建收购案例记录
383
+ const acquisitionId = db.genId();
384
+ const now = new Date().toISOString();
385
+ db.execute(
386
+ `INSERT INTO opc_acquisition_cases
387
+ (id, company_id, acquirer_id, case_type, status, trigger_reason, acquisition_price, loss_amount, tax_deduction, created_at, updated_at)
388
+ VALUES (?, ?, 'starriver', 'acquisition', 'evaluating', '连续亏损', ?, ?, ?, ?, ?)`,
389
+ acquisitionId, company.id, 10000, 15000, 3750, now, now
390
+ );
391
+
392
+ const acquisitionCase = db.queryOne(
393
+ "SELECT * FROM opc_acquisition_cases WHERE id = ?",
394
+ acquisitionId
395
+ ) as any;
396
+ expect(acquisitionCase).not.toBeNull();
397
+ expect(acquisitionCase.loss_amount).toBe(15000);
398
+ });
399
+ });