Files
loom/backend/app/scripts/seed_p4.py
T
2026-04-24 17:46:03 +08:00

170 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""P4 种子:默认平台 + 脚本模板 + 一个示例 Skill(LOBSTER 模式)。
执行:python -m app.scripts.seed_p4
可重复运行。
"""
from __future__ import annotations
import asyncio
from sqlalchemy import delete, insert, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import AsyncSessionLocal
from app.models import (
ChannelCode,
Platform,
Product,
ScriptTemplate,
Skill,
product_channel_code,
skill_product_scope,
)
DEFAULT_PLATFORMS = [
{"code": "facebook", "name": "Facebook", "url": "https://facebook.com"},
{"code": "tiktok", "name": "TikTok", "url": "https://tiktok.com"},
{"code": "google", "name": "Google Ads", "url": "https://ads.google.com"},
]
DEFAULT_CHANNELS = [
{"code": "FB_INT_01", "name": "FB 内导 01", "type": "INTERNAL"},
{"code": "FB_NAT_01", "name": "FB 自然 01", "type": "NATURAL"},
{"code": "TT_INT_01", "name": "TT 内导 01", "type": "INTERNAL"},
]
DEFAULT_SCRIPTS = [
{
"code": "py_hello",
"name": "Python Hello",
"language": "python",
"description": "示例脚本,输出欢迎信息",
"content": 'print("hello from loom script")\n',
"timeout_seconds": 30,
},
{
"code": "sh_curl_demo",
"name": "Shell curl 演示",
"language": "shell",
"description": "示例 curl 调用",
"content": 'echo "fetching..."\ncurl -s https://httpbin.org/ip\n',
"timeout_seconds": 60,
},
]
DEFAULT_SKILL = {
"code": "fb_lobster_post",
"name": "Facebook 发帖(龙虾)",
"category": "social",
"executor_type": "LOBSTER",
"description": "调用龙虾模板完成 Facebook 发帖",
"input_schema": {
"type": "object",
"required": ["title", "content"],
"properties": {
"title": {"type": "string", "title": "标题", "maxLength": 80},
"content": {"type": "string", "title": "正文", "format": "textarea"},
"image_url": {"type": "string", "title": "图片链接"},
"schedule_at": {"type": "string", "title": "定时时间", "format": "date-time"},
},
},
"param_mapping": {
"post_title": "{{input.title}}",
"post_body": "{{input.content}}",
"image_url": "{{input.image_url}}",
"scheduled": "{{input.schedule_at}}",
},
"lobster_template_id": "lobster-demo-001",
"doc": "演示 Skill:LOBSTER 执行器 + 产品范围 + JSON Schema 参数。.env 配置 LOBSTER_API_KEY 后自动走真实龙虾 API,否则走客户端内置 mock。",
}
async def upsert(db: AsyncSession, model, key_field: str, value, **defaults):
stmt = select(model).where(getattr(model, key_field) == value)
obj = (await db.execute(stmt)).scalar_one_or_none()
if obj is None:
obj = model(**{key_field: value}, **defaults)
db.add(obj)
await db.flush()
else:
for k, v in defaults.items():
setattr(obj, k, v)
return obj
async def seed() -> None:
async with AsyncSessionLocal() as db:
platforms = {}
for p in DEFAULT_PLATFORMS:
obj = await upsert(db, Platform, "code", p["code"],
name=p["name"], url=p["url"])
platforms[p["code"]] = obj
channels = {}
for c in DEFAULT_CHANNELS:
obj = await upsert(db, ChannelCode, "code", c["code"],
name=c["name"], type=c["type"])
channels[c["code"]] = obj
# 一个示例产品:FB / 产品代号 fb_demo
fb_product = await upsert(
db, Product, "code", "fb_demo",
name="FB 示例产品",
platform_id=platforms["facebook"].id,
description="P4 演示用产品",
)
# 绑定 2 个渠道码
await db.execute(
delete(product_channel_code).where(
product_channel_code.c.product_id == fb_product.id
)
)
await db.execute(
insert(product_channel_code),
[
{"product_id": fb_product.id, "channel_code_id": channels["FB_INT_01"].id},
{"product_id": fb_product.id, "channel_code_id": channels["FB_NAT_01"].id},
],
)
for s in DEFAULT_SCRIPTS:
await upsert(db, ScriptTemplate, "code", s["code"], **{
"name": s["name"],
"language": s["language"],
"description": s["description"],
"content": s["content"],
"timeout_seconds": s["timeout_seconds"],
})
# 示例 Skill
stmt = select(Skill).where(Skill.code == DEFAULT_SKILL["code"])
skill = (await db.execute(stmt)).scalar_one_or_none()
if skill is None:
skill = Skill(**DEFAULT_SKILL)
db.add(skill)
await db.flush()
else:
for k, v in DEFAULT_SKILL.items():
setattr(skill, k, v)
await db.execute(
delete(skill_product_scope).where(skill_product_scope.c.skill_id == skill.id)
)
await db.execute(
insert(skill_product_scope),
[{"skill_id": skill.id, "product_id": fb_product.id}],
)
await db.commit()
print("[seed_p4] done.")
print(f" platforms: {[p['code'] for p in DEFAULT_PLATFORMS]}")
print(f" channels: {[c['code'] for c in DEFAULT_CHANNELS]}")
print(f" scripts: {[s['code'] for s in DEFAULT_SCRIPTS]}")
print(f" product: fb_demo (2 channels)")
print(f" skill: {DEFAULT_SKILL['code']} (LOBSTER, scope=[fb_demo])")
if __name__ == "__main__":
asyncio.run(seed())