In [109]:
!pip install numpy ipywidgets
Requirement already satisfied: numpy in /opt/anaconda3/lib/python3.12/site-packages (1.26.4) Requirement already satisfied: ipywidgets in /opt/anaconda3/lib/python3.12/site-packages (7.8.1) Requirement already satisfied: comm>=0.1.3 in /opt/anaconda3/lib/python3.12/site-packages (from ipywidgets) (0.2.1) Requirement already satisfied: ipython-genutils~=0.2.0 in /opt/anaconda3/lib/python3.12/site-packages (from ipywidgets) (0.2.0) Requirement already satisfied: traitlets>=4.3.1 in /opt/anaconda3/lib/python3.12/site-packages (from ipywidgets) (5.14.3) Requirement already satisfied: widgetsnbextension~=3.6.6 in /opt/anaconda3/lib/python3.12/site-packages (from ipywidgets) (3.6.6) Requirement already satisfied: ipython>=4.0.0 in /opt/anaconda3/lib/python3.12/site-packages (from ipywidgets) (8.27.0) Requirement already satisfied: jupyterlab-widgets<3,>=1.0.0 in /opt/anaconda3/lib/python3.12/site-packages (from ipywidgets) (1.0.0) Requirement already satisfied: decorator in /opt/anaconda3/lib/python3.12/site-packages (from ipython>=4.0.0->ipywidgets) (5.1.1) Requirement already satisfied: jedi>=0.16 in /opt/anaconda3/lib/python3.12/site-packages (from ipython>=4.0.0->ipywidgets) (0.19.1) Requirement already satisfied: matplotlib-inline in /opt/anaconda3/lib/python3.12/site-packages (from ipython>=4.0.0->ipywidgets) (0.1.6) Requirement already satisfied: prompt-toolkit<3.1.0,>=3.0.41 in /opt/anaconda3/lib/python3.12/site-packages (from ipython>=4.0.0->ipywidgets) (3.0.43) Requirement already satisfied: pygments>=2.4.0 in /opt/anaconda3/lib/python3.12/site-packages (from ipython>=4.0.0->ipywidgets) (2.15.1) Requirement already satisfied: stack-data in /opt/anaconda3/lib/python3.12/site-packages (from ipython>=4.0.0->ipywidgets) (0.2.0) Requirement already satisfied: pexpect>4.3 in /opt/anaconda3/lib/python3.12/site-packages (from ipython>=4.0.0->ipywidgets) (4.8.0) Requirement already satisfied: notebook>=4.4.1 in /opt/anaconda3/lib/python3.12/site-packages (from widgetsnbextension~=3.6.6->ipywidgets) (7.2.2) Requirement already satisfied: parso<0.9.0,>=0.8.3 in /opt/anaconda3/lib/python3.12/site-packages (from jedi>=0.16->ipython>=4.0.0->ipywidgets) (0.8.3) Requirement already satisfied: jupyter-server<3,>=2.4.0 in /opt/anaconda3/lib/python3.12/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.14.1) Requirement already satisfied: jupyterlab-server<3,>=2.27.1 in /opt/anaconda3/lib/python3.12/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.27.3) Requirement already satisfied: jupyterlab<4.3,>=4.2.0 in /opt/anaconda3/lib/python3.12/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (4.2.5) Requirement already satisfied: notebook-shim<0.3,>=0.2 in /opt/anaconda3/lib/python3.12/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.2.3) Requirement already satisfied: tornado>=6.2.0 in /opt/anaconda3/lib/python3.12/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (6.4.1) Requirement already satisfied: ptyprocess>=0.5 in /opt/anaconda3/lib/python3.12/site-packages (from pexpect>4.3->ipython>=4.0.0->ipywidgets) (0.7.0) Requirement already satisfied: wcwidth in /opt/anaconda3/lib/python3.12/site-packages (from prompt-toolkit<3.1.0,>=3.0.41->ipython>=4.0.0->ipywidgets) (0.2.5) Requirement already satisfied: executing in /opt/anaconda3/lib/python3.12/site-packages (from stack-data->ipython>=4.0.0->ipywidgets) (0.8.3) Requirement already satisfied: asttokens in /opt/anaconda3/lib/python3.12/site-packages (from stack-data->ipython>=4.0.0->ipywidgets) (2.0.5) Requirement already satisfied: pure-eval in /opt/anaconda3/lib/python3.12/site-packages (from stack-data->ipython>=4.0.0->ipywidgets) (0.2.2) Requirement already satisfied: anyio>=3.1.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (4.2.0) Requirement already satisfied: argon2-cffi>=21.1 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (21.3.0) Requirement already satisfied: jinja2>=3.0.3 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (3.1.4) Requirement already satisfied: jupyter-client>=7.4.4 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (8.6.0) Requirement already satisfied: jupyter-core!=5.0.*,>=4.12 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (5.7.2) Requirement already satisfied: jupyter-events>=0.9.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.10.0) Requirement already satisfied: jupyter-server-terminals>=0.4.4 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.4.4) Requirement already satisfied: nbconvert>=6.4.4 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (7.16.4) Requirement already satisfied: nbformat>=5.3.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (5.10.4) Requirement already satisfied: overrides>=5.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (7.4.0) Requirement already satisfied: packaging>=22.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (24.1) Requirement already satisfied: prometheus-client>=0.9 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.14.1) Requirement already satisfied: pyzmq>=24 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (25.1.2) Requirement already satisfied: send2trash>=1.8.2 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.8.2) Requirement already satisfied: terminado>=0.8.3 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.17.1) Requirement already satisfied: websocket-client>=1.7 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.8.0) Requirement already satisfied: async-lru>=1.0.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.0.4) Requirement already satisfied: httpx>=0.25.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.27.0) Requirement already satisfied: ipykernel>=6.5.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (6.28.0) Requirement already satisfied: jupyter-lsp>=2.0.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.2.0) Requirement already satisfied: setuptools>=40.1.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (75.1.0) Requirement already satisfied: babel>=2.10 in /opt/anaconda3/lib/python3.12/site-packages (from jupyterlab-server<3,>=2.27.1->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.11.0) Requirement already satisfied: json5>=0.9.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyterlab-server<3,>=2.27.1->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.9.6) Requirement already satisfied: jsonschema>=4.18.0 in /opt/anaconda3/lib/python3.12/site-packages (from jupyterlab-server<3,>=2.27.1->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (4.23.0) Requirement already satisfied: requests>=2.31 in /opt/anaconda3/lib/python3.12/site-packages (from jupyterlab-server<3,>=2.27.1->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.32.3) Requirement already satisfied: six in /opt/anaconda3/lib/python3.12/site-packages (from asttokens->stack-data->ipython>=4.0.0->ipywidgets) (1.16.0) Requirement already satisfied: idna>=2.8 in /opt/anaconda3/lib/python3.12/site-packages (from anyio>=3.1.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (3.7) Requirement already satisfied: sniffio>=1.1 in /opt/anaconda3/lib/python3.12/site-packages (from anyio>=3.1.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.3.0) Requirement already satisfied: argon2-cffi-bindings in /opt/anaconda3/lib/python3.12/site-packages (from argon2-cffi>=21.1->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (21.2.0) Requirement already satisfied: pytz>=2015.7 in /opt/anaconda3/lib/python3.12/site-packages (from babel>=2.10->jupyterlab-server<3,>=2.27.1->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2024.1) Requirement already satisfied: certifi in /opt/anaconda3/lib/python3.12/site-packages (from httpx>=0.25.0->jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2025.4.26) Requirement already satisfied: httpcore==1.* in /opt/anaconda3/lib/python3.12/site-packages (from httpx>=0.25.0->jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.0.2) Requirement already satisfied: h11<0.15,>=0.13 in /opt/anaconda3/lib/python3.12/site-packages (from httpcore==1.*->httpx>=0.25.0->jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.14.0) Requirement already satisfied: appnope in /opt/anaconda3/lib/python3.12/site-packages (from ipykernel>=6.5.0->jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.1.3) Requirement already satisfied: debugpy>=1.6.5 in /opt/anaconda3/lib/python3.12/site-packages (from ipykernel>=6.5.0->jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.6.7) Requirement already satisfied: nest-asyncio in /opt/anaconda3/lib/python3.12/site-packages (from ipykernel>=6.5.0->jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.6.0) Requirement already satisfied: psutil in /opt/anaconda3/lib/python3.12/site-packages (from ipykernel>=6.5.0->jupyterlab<4.3,>=4.2.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (5.9.0) Requirement already satisfied: MarkupSafe>=2.0 in /opt/anaconda3/lib/python3.12/site-packages (from jinja2>=3.0.3->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.1.3) Requirement already satisfied: attrs>=22.2.0 in /opt/anaconda3/lib/python3.12/site-packages (from jsonschema>=4.18.0->jupyterlab-server<3,>=2.27.1->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (23.1.0) Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/anaconda3/lib/python3.12/site-packages (from jsonschema>=4.18.0->jupyterlab-server<3,>=2.27.1->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2023.7.1) Requirement already satisfied: referencing>=0.28.4 in /opt/anaconda3/lib/python3.12/site-packages (from jsonschema>=4.18.0->jupyterlab-server<3,>=2.27.1->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.30.2) Requirement already satisfied: rpds-py>=0.7.1 in /opt/anaconda3/lib/python3.12/site-packages (from jsonschema>=4.18.0->jupyterlab-server<3,>=2.27.1->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.10.6) Requirement already satisfied: python-dateutil>=2.8.2 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-client>=7.4.4->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.9.0.post0) Requirement already satisfied: platformdirs>=2.5 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-core!=5.0.*,>=4.12->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (3.10.0) Requirement already satisfied: python-json-logger>=2.0.4 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.0.7) Requirement already satisfied: pyyaml>=5.3 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (6.0.1) Requirement already satisfied: rfc3339-validator in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.1.4) Requirement already satisfied: rfc3986-validator>=0.1.1 in /opt/anaconda3/lib/python3.12/site-packages (from jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.1.1) Requirement already satisfied: beautifulsoup4 in /opt/anaconda3/lib/python3.12/site-packages (from nbconvert>=6.4.4->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (4.12.3) Requirement already satisfied: bleach!=5.0.0 in /opt/anaconda3/lib/python3.12/site-packages (from nbconvert>=6.4.4->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (4.1.0) Requirement already satisfied: defusedxml in /opt/anaconda3/lib/python3.12/site-packages (from nbconvert>=6.4.4->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.7.1) Requirement already satisfied: jupyterlab-pygments in /opt/anaconda3/lib/python3.12/site-packages (from nbconvert>=6.4.4->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.1.2) Requirement already satisfied: mistune<4,>=2.0.3 in /opt/anaconda3/lib/python3.12/site-packages (from nbconvert>=6.4.4->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.0.4) Requirement already satisfied: nbclient>=0.5.0 in /opt/anaconda3/lib/python3.12/site-packages (from nbconvert>=6.4.4->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.8.0) Requirement already satisfied: pandocfilters>=1.4.1 in /opt/anaconda3/lib/python3.12/site-packages (from nbconvert>=6.4.4->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.5.0) Requirement already satisfied: tinycss2 in /opt/anaconda3/lib/python3.12/site-packages (from nbconvert>=6.4.4->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.2.1) Requirement already satisfied: fastjsonschema>=2.15 in /opt/anaconda3/lib/python3.12/site-packages (from nbformat>=5.3.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.16.2) Requirement already satisfied: charset-normalizer<4,>=2 in /opt/anaconda3/lib/python3.12/site-packages (from requests>=2.31->jupyterlab-server<3,>=2.27.1->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (3.3.2) Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/anaconda3/lib/python3.12/site-packages (from requests>=2.31->jupyterlab-server<3,>=2.27.1->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.2.3) Requirement already satisfied: webencodings in /opt/anaconda3/lib/python3.12/site-packages (from bleach!=5.0.0->nbconvert>=6.4.4->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (0.5.1) Requirement already satisfied: fqdn in /opt/anaconda3/lib/python3.12/site-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.5.1) Requirement already satisfied: isoduration in /opt/anaconda3/lib/python3.12/site-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (20.11.0) Requirement already satisfied: jsonpointer>1.13 in /opt/anaconda3/lib/python3.12/site-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.1) Requirement already satisfied: uri-template in /opt/anaconda3/lib/python3.12/site-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.3.0) Requirement already satisfied: webcolors>=24.6.0 in /opt/anaconda3/lib/python3.12/site-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (25.10.0) Requirement already satisfied: cffi>=1.0.1 in /opt/anaconda3/lib/python3.12/site-packages (from argon2-cffi-bindings->argon2-cffi>=21.1->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.17.1) Requirement already satisfied: soupsieve>1.2 in /opt/anaconda3/lib/python3.12/site-packages (from beautifulsoup4->nbconvert>=6.4.4->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.5) Requirement already satisfied: pycparser in /opt/anaconda3/lib/python3.12/site-packages (from cffi>=1.0.1->argon2-cffi-bindings->argon2-cffi>=21.1->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (2.21) Requirement already satisfied: arrow>=0.15.0 in /opt/anaconda3/lib/python3.12/site-packages (from isoduration->jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=2.4.0->notebook>=4.4.1->widgetsnbextension~=3.6.6->ipywidgets) (1.2.3)
In [110]:
!pip install chardet --quiet
In [111]:
# Cell 1
import json, math
from pathlib import Path
from datetime import datetime
import numpy as np
from IPython.display import display, Markdown, HTML, clear_output
import ipywidgets as widgets
OUT_DIR = Path("demo_outputs")
OUT_DIR.mkdir(exist_ok=True)
def save_json(obj, name):
p = OUT_DIR / name
p.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"Saved {p.resolve()}")
def show(obj, title=None):
if title: display(Markdown(f"### {title}"))
display(HTML(f"<pre>{json.dumps(obj, ensure_ascii=False, indent=2)}</pre>"))
In [112]:
# Cell 2
questionnaire = {
"title":"KOC 风格自评问卷(快速版,6题)",
"questions":[
{"id":"q1","text":"当你上镜讲内容时,你更偏向哪种方式?","options":[
{"id":"q1a","text":"讲故事/生活化","scores":{"Humor":10,"Emotionality":70,"Rationality":20,"Pace":40,"Spontaneity":50}},
{"id":"q1b","text":"讲数据/功能","scores":{"Humor":5,"Emotionality":20,"Rationality":80,"Pace":40,"Spontaneity":30}},
{"id":"q1c","text":"幽默/夸张","scores":{"Humor":90,"Emotionality":50,"Rationality":10,"Pace":80,"Spontaneity":80}},
{"id":"q1d","text":"平和稳重","scores":{"Humor":5,"Emotionality":50,"Rationality":50,"Pace":20,"Spontaneity":20}}
]},
{"id":"q2","text":"面对镜头,你的舒适度?","options":[
{"id":"q2a","text":"非常自在","scores":{"Humor":40,"Emotionality":40,"Rationality":30,"Pace":60,"Spontaneity":90}},
{"id":"q2b","text":"略微紧张","scores":{"Humor":20,"Emotionality":40,"Rationality":50,"Pace":40,"Spontaneity":50}},
{"id":"q2c","text":"倾向背台词","scores":{"Humor":10,"Emotionality":30,"Rationality":70,"Pace":30,"Spontaneity":20}},
{"id":"q2d","text":"不太习惯","scores":{"Humor":5,"Emotionality":20,"Rationality":60,"Pace":20,"Spontaneity":10}}
]},
{"id":"q3","text":"视频节奏偏好?","options":[
{"id":"q3a","text":"快节奏","scores":{"Humor":60,"Emotionality":50,"Rationality":20,"Pace":90,"Spontaneity":70}},
{"id":"q3b","text":"中速","scores":{"Humor":30,"Emotionality":50,"Rationality":50,"Pace":50,"Spontaneity":50}},
{"id":"q3c","text":"慢讲解","scores":{"Humor":10,"Emotionality":40,"Rationality":80,"Pace":20,"Spontaneity":30}},
{"id":"q3d","text":"场景铺陈","scores":{"Humor":20,"Emotionality":80,"Rationality":30,"Pace":30,"Spontaneity":40}}
]},
{"id":"q4","text":"希望给人印象?","options":[
{"id":"q4a","text":"有趣/亲切","scores":{"Humor":80,"Emotionality":60,"Rationality":20,"Pace":60,"Spontaneity":60}},
{"id":"q4b","text":"专业/靠谱","scores":{"Humor":10,"Emotionality":30,"Rationality":90,"Pace":40,"Spontaneity":30}},
{"id":"q4c","text":"温暖/可信","scores":{"Humor":20,"Emotionality":80,"Rationality":40,"Pace":30,"Spontaneity":40}},
{"id":"q4d","text":"真诚/接地气","scores":{"Humor":20,"Emotionality":70,"Rationality":40,"Pace":30,"Spontaneity":50}}
]},
{"id":"q5","text":"能接受的改动幅度?","options":[
{"id":"q5a","text":"大幅尝试","scores":{"Humor":40,"Emotionality":40,"Rationality":40,"Pace":40,"Spontaneity":80}},
{"id":"q5b","text":"中度尝试","scores":{"Humor":30,"Emotionality":30,"Rationality":50,"Pace":50,"Spontaneity":60}},
{"id":"q5c","text":"轻微调整","scores":{"Humor":10,"Emotionality":20,"Rationality":60,"Pace":30,"Spontaneity":30}},
{"id":"q5d","text":"不愿改变","scores":{"Humor":5,"Emotionality":10,"Rationality":70,"Pace":20,"Spontaneity":10}}
]},
{"id":"q6","text":"形象训练投入?","options":[
{"id":"q6a","text":"高","scores":{"Humor":40,"Emotionality":40,"Rationality":30,"Pace":60,"Spontaneity":80}},
{"id":"q6b","text":"中","scores":{"Humor":30,"Emotionality":30,"Rationality":50,"Pace":40,"Spontaneity":50}},
{"id":"q6c","text":"低","scores":{"Humor":15,"Emotionality":20,"Rationality":60,"Pace":30,"Spontaneity":30}},
{"id":"q6d","text":"极低","scores":{"Humor":5,"Emotionality":10,"Rationality":70,"Pace":20,"Spontaneity":10}}
]}
],
"post_processing":{"method":"sum_then_normalize","weight_questionnaire":0.4}
}
save_json(questionnaire, "questionnaire.json")
print("Questionnaire saved.")
Saved /Users/vallenliu/demo_outputs/questionnaire.json Questionnaire saved.
In [113]:
# Cell 3
from datetime import datetime, timezone
def compute_style_vector_from_answers(answers, questionnaire):
dims = {"Humor":0,"Emotionality":0,"Rationality":0,"Pace":0,"Spontaneity":0}
for q, ans in zip(questionnaire["questions"], answers):
opt = next(o for o in q["options"] if o["id"]==ans)
for k,v in opt["scores"].items():
dims[k] += v
max_per = len(questionnaire["questions"]) * 100
style = {k: round(v / max_per * 100,1) for k,v in dims.items()}
# naive confidence: higher mean -> higher confidence (placeholder)
conf = round(sum(style.values())/(len(style)*100),2)
return style, conf
# Demo answers (pick first option for simplicity)
demo_answers = [q["options"][0]["id"] for q in questionnaire["questions"]]
style_vec, style_conf = compute_style_vector_from_answers(demo_answers, questionnaire)
koc_profile = {
"id":"koc_demo_001",
"name":"Demo KOC",
"style_vector": {k: {"val":v, "conf":style_conf} for k,v in style_vec.items()},
"capability":{"shoot":60,"edit":50,"improv":40},
"preferences":{"categories":["护肤","美妆"], "price_range":"low"},
"consent_flags":{"orders":False,"followers":False},
"created_at": datetime.now(timezone.utc).isoformat()
}
save_json(koc_profile, "koc_profile.json")
show(koc_profile, "KOC Profile (module A)")
Saved /Users/vallenliu/demo_outputs/koc_profile.json
KOC Profile (module A)¶
{
"id": "koc_demo_001",
"name": "Demo KOC",
"style_vector": {
"Humor": {
"val": 45.0,
"conf": 0.5
},
"Emotionality": {
"val": 50.0,
"conf": 0.5
},
"Rationality": {
"val": 26.7,
"conf": 0.5
},
"Pace": {
"val": 58.3,
"conf": 0.5
},
"Spontaneity": {
"val": 71.7,
"conf": 0.5
}
},
"capability": {
"shoot": 60,
"edit": 50,
"improv": 40
},
"preferences": {
"categories": [
"护肤",
"美妆"
],
"price_range": "low"
},
"consent_flags": {
"orders": false,
"followers": false
},
"created_at": "2026-04-25T02:35:47.882233+00:00"
}
In [114]:
# Cell 4
def generate_audience_from_history_or_coldstart(koc_profile, has_history=False):
if has_history:
# placeholder: analyze history -> produce segments
segments = [{"id":"s1","label":"女性25-34敏感肌","age_range":[25,34],"gender":"F","interest_tags":["护肤"],"conf":0.85,"sample_size":1200}]
else:
# cold start: use declared prefs + platform heuristics
segments = [{"id":"s_demo","label":"默认年轻女性","age_range":[18,34],"gender":"F","interest_tags":koc_profile["preferences"]["categories"],"conf":0.45,"sample_size":80}]
return {"koc_id":koc_profile["id"], "segments":segments, "suggested_test_plan":"Run 2 variants in private group, 500 impressions each"}
aud_profile = generate_audience_from_history_or_coldstart(koc_profile, has_history=False)
save_json(aud_profile, "audience_profile.json")
show(aud_profile, "Audience Profile (module B)")
Saved /Users/vallenliu/demo_outputs/audience_profile.json
Audience Profile (module B)¶
{
"koc_id": "koc_demo_001",
"segments": [
{
"id": "s_demo",
"label": "默认年轻女性",
"age_range": [
18,
34
],
"gender": "F",
"interest_tags": [
"护肤",
"美妆"
],
"conf": 0.45,
"sample_size": 80
}
],
"suggested_test_plan": "Run 2 variants in private group, 500 impressions each"
}
In [115]:
# Cell 5
def norm_margin(margin_pct): return min(1.0, margin_pct/0.4)
def norm_inventory(inv, thr=1000): return min(1.0, inv/thr)
def norm_lead(days, maxd=14): return max(0.0, (maxd - min(days, maxd))/maxd)
def norm_sample(can_sample, sample_days=None, maxd=7):
if can_sample:
if sample_days is None: return 1.0
return max(0.0, (maxd - min(sample_days, maxd))/maxd)
return 0.0
def compute_supply_score(candidate, weights=None):
if weights is None:
weights = {"w1":0.30,"w2":0.25,"w3":0.15,"w4":0.15,"w5":0.15}
ss = candidate.get("supply_signals",{})
T = norm_sample(ss.get("can_sample", False), ss.get("sample_time_days", None))
M = norm_margin(candidate.get("margin_pct",0.2))
I = norm_inventory(ss.get("inventory",0))
L = norm_lead(ss.get("lead_time_days",14))
B = candidate.get("brand_score",50)/100.0
num = weights["w1"]*T + weights["w2"]*M + weights["w3"]*I + weights["w4"]*L + weights["w5"]*B
return round(100 * num / sum(weights.values()),1)
candidate = {
"id":"sku123","name":"护肤X霜(敏感型)","brand":"小众牌A","price":79.0,"source_tag":"护肤",
"evidence":[{"type":"ecom_trend","text":"近30天搜索↑45%","url":""}],
"supply_signals":{"can_sample":True,"sample_time_days":5,"inventory":800,"lead_time_days":3},
"margin_pct":0.35,"brand_score":78
}
supply_score = compute_supply_score(candidate)
save_json(candidate, "product_candidate.json")
print("supply_score:", supply_score)
show({"supply_score":supply_score}, "Supply Score (module C)")
Saved /Users/vallenliu/demo_outputs/product_candidate.json supply_score: 65.9
Supply Score (module C)¶
{
"supply_score": 65.9
}
In [116]:
# Cell 6
def simple_audience_match(candidate, audience_profile):
seg = audience_profile["segments"][0]
score = 0.0
if candidate.get("source_tag") in seg.get("interest_tags",[]):
score += 0.6
pref_price = "low" # simplified
price = candidate.get("price", 0)
if pref_price=="low" and price<100: score += 0.4
return round(min(1.0,score)*100,1)
def rank_score(candidate, audience_profile, trend_score=50, alpha=0.5, beta=0.35, gamma=0.15):
a = simple_audience_match(candidate, audience_profile)
s = compute_supply_score(candidate)
return round(alpha*a + beta*s + gamma*trend_score,1)
aud_match = simple_audience_match(candidate, aud_profile)
rank = rank_score(candidate, aud_profile)
candidates_list = [candidate]
for c in candidates_list:
c["supply_score"] = compute_supply_score(c)
c["audience_match_score"] = simple_audience_match(c, aud_profile)
c["rank_score"] = rank_score(c, aud_profile)
save_json({"candidates":candidates_list}, "candidates_list.json")
show(candidates_list, "Candidates (module C output)")
Saved /Users/vallenliu/demo_outputs/candidates_list.json
Candidates (module C output)¶
[
{
"id": "sku123",
"name": "护肤X霜(敏感型)",
"brand": "小众牌A",
"price": 79.0,
"source_tag": "护肤",
"evidence": [
{
"type": "ecom_trend",
"text": "近30天搜索↑45%",
"url": ""
}
],
"supply_signals": {
"can_sample": true,
"sample_time_days": 5,
"inventory": 800,
"lead_time_days": 3
},
"margin_pct": 0.35,
"brand_score": 78,
"supply_score": 65.9,
"audience_match_score": 100.0,
"rank_score": 80.6
}
]
In [117]:
# Cell 7
def generate_script_variants(koc_profile, candidate, num=3):
hooks = ["最近用了这个,效果挺让我惊喜。","性价比超高,值得一试。","真心推荐,尤其适合你们。"]
tones = ["温和可信","中性推荐","情绪化种草"]
variants=[]
for i in range(num):
variants.append({
"id":f"scr_{candidate['id']}_{i}",
"hook":hooks[i%len(hooks)],
"body":f"产品:{candidate['name']},价格{candidate['price']}元,卖点:{candidate['evidence'][0]['text']}",
"cta":"点击链接了解/私信拿优惠",
"style_label":tones[i%len(tones)],
"target_segment":aud_profile["segments"][0]["id"],
"evidence":candidate["evidence"]
})
return variants
scripts = generate_script_variants(koc_profile, candidate, num=3)
save_json({"scripts":scripts}, "scripts.json")
show(scripts, "Scripts (module D)")
Saved /Users/vallenliu/demo_outputs/scripts.json
Scripts (module D)¶
[
{
"id": "scr_sku123_0",
"hook": "最近用了这个,效果挺让我惊喜。",
"body": "产品:护肤X霜(敏感型),价格79.0元,卖点:近30天搜索↑45%",
"cta": "点击链接了解/私信拿优惠",
"style_label": "温和可信",
"target_segment": "s_demo",
"evidence": [
{
"type": "ecom_trend",
"text": "近30天搜索↑45%",
"url": ""
}
]
},
{
"id": "scr_sku123_1",
"hook": "性价比超高,值得一试。",
"body": "产品:护肤X霜(敏感型),价格79.0元,卖点:近30天搜索↑45%",
"cta": "点击链接了解/私信拿优惠",
"style_label": "中性推荐",
"target_segment": "s_demo",
"evidence": [
{
"type": "ecom_trend",
"text": "近30天搜索↑45%",
"url": ""
}
]
},
{
"id": "scr_sku123_2",
"hook": "真心推荐,尤其适合你们。",
"body": "产品:护肤X霜(敏感型),价格79.0元,卖点:近30天搜索↑45%",
"cta": "点击链接了解/私信拿优惠",
"style_label": "情绪化种草",
"target_segment": "s_demo",
"evidence": [
{
"type": "ecom_trend",
"text": "近30天搜索↑45%",
"url": ""
}
]
}
]
In [118]:
# Cell 8
def gen_storyboard(script_text, style_vector, length_sec=60):
seg = length_sec // 3
sb=[]
for i in range(3):
sb.append({"start":i*seg,"end":(i+1)*seg if i<2 else length_sec,
"action":["钩子","核心卖点","结尾CTA"][i],
"camera":["近景","中景","特写"][i],
"dialogue":f"第{i+1}段示例台词","cues":["表情","展示细节","强调CTA"][i]})
return sb
def gen_subtitles(storyboard):
srt=[]
for i,s in enumerate(storyboard,1):
srt.append({"index":i,"start":f"00:00:{s['start']:02d}","end":f"00:00:{s['end']:02d}","text":s["dialogue"]})
return srt
def gen_cut_list(storyboard):
return [{"start":s["start"],"end":s["end"],"reason":s["action"],"replace_with":"b-roll_closeup"} for s in storyboard]
def gen_makeup_plan(candidate, koc_profile):
if "护肤" in candidate.get("source_tag",""):
return {"level":"推荐","steps":["清洁","保湿","轻薄粉底","自然腮红"],"color_palette":["#F2C1C1","#FFF6EA"],"confidence":0.78}
return {"level":"简易","steps":["自然妆"],"confidence":0.5}
storyboard = gen_storyboard("示例脚本", koc_profile["style_vector"], length_sec=60)
subtitles = gen_subtitles(storyboard)
cut_list = gen_cut_list(storyboard)
makeup_plan = gen_makeup_plan(candidate, koc_profile)
material = {
"storyboard":storyboard,
"subtitles":subtitles,
"cut_list":cut_list,
"makeup_plan":makeup_plan,
"covers":[{"url":"cover1.png","prompt":"面部特写+大字钩子","ctr_score":0.12,"uncertainty":0.03}],
"variants":[{"id":"v1","desc":"快节奏钩子"},{"id":"v2","desc":"慢节奏讲解"}]
}
save_json(material, "material_package.json")
show(material, "Material Package (module E)")
Saved /Users/vallenliu/demo_outputs/material_package.json
Material Package (module E)¶
{
"storyboard": [
{
"start": 0,
"end": 20,
"action": "钩子",
"camera": "近景",
"dialogue": "第1段示例台词",
"cues": "表情"
},
{
"start": 20,
"end": 40,
"action": "核心卖点",
"camera": "中景",
"dialogue": "第2段示例台词",
"cues": "展示细节"
},
{
"start": 40,
"end": 60,
"action": "结尾CTA",
"camera": "特写",
"dialogue": "第3段示例台词",
"cues": "强调CTA"
}
],
"subtitles": [
{
"index": 1,
"start": "00:00:00",
"end": "00:00:20",
"text": "第1段示例台词"
},
{
"index": 2,
"start": "00:00:20",
"end": "00:00:40",
"text": "第2段示例台词"
},
{
"index": 3,
"start": "00:00:40",
"end": "00:00:60",
"text": "第3段示例台词"
}
],
"cut_list": [
{
"start": 0,
"end": 20,
"reason": "钩子",
"replace_with": "b-roll_closeup"
},
{
"start": 20,
"end": 40,
"reason": "核心卖点",
"replace_with": "b-roll_closeup"
},
{
"start": 40,
"end": 60,
"reason": "结尾CTA",
"replace_with": "b-roll_closeup"
}
],
"makeup_plan": {
"level": "推荐",
"steps": [
"清洁",
"保湿",
"轻薄粉底",
"自然腮红"
],
"color_palette": [
"#F2C1C1",
"#FFF6EA"
],
"confidence": 0.78
},
"covers": [
{
"url": "cover1.png",
"prompt": "面部特写+大字钩子",
"ctr_score": 0.12,
"uncertainty": 0.03
}
],
"variants": [
{
"id": "v1",
"desc": "快节奏钩子"
},
{
"id": "v2",
"desc": "慢节奏讲解"
}
]
}
In [119]:
# Cell 9
from datetime import datetime, timezone
def recommend_publish_plan(audience_profile):
now = datetime.now(timezone.utc)
plan = {
"primary_time": (now.replace(hour=20, minute=0)).isoformat(),
"backup_time": (now.replace(hour=12, minute=0)).isoformat(),
"channel": "视频号",
"frequency_per_week": 3,
"tags": ["护肤", "敏感肌"],
"reason": "目标受众晚间活跃,历史相似案例完成率高"
}
return plan
publish_plan = recommend_publish_plan(aud_profile)
save_json(publish_plan, "publish_plan.json")
show(publish_plan, "Publish Plan (module F)")
Saved /Users/vallenliu/demo_outputs/publish_plan.json
Publish Plan (module F)¶
{
"primary_time": "2026-04-25T20:00:50.871893+00:00",
"backup_time": "2026-04-25T12:00:50.871893+00:00",
"channel": "视频号",
"frequency_per_week": 3,
"tags": [
"护肤",
"敏感肌"
],
"reason": "目标受众晚间活跃,历史相似案例完成率高"
}
In [120]:
# 定义 A/B 判定函数(如果之前未定义或丢失,运行此 cell)
import numpy as np
def posterior_prob_B_gt_A(successes_A, trials_A, successes_B, trials_B,
alpha0=2.0, beta0=2.0, n_samples=20000, seed=42):
rng = np.random.default_rng(seed)
a_A = alpha0 + int(successes_A)
b_A = beta0 + int(trials_A) - int(successes_A)
a_B = alpha0 + int(successes_B)
b_B = beta0 + int(trials_B) - int(successes_B)
samples_A = rng.beta(a_A, b_A, size=n_samples)
samples_B = rng.beta(a_B, b_B, size=n_samples)
posterior_prob = float(np.mean(samples_B > samples_A))
mean_A = a_A / (a_A + b_A)
mean_B = a_B / (a_B + b_B)
return posterior_prob, mean_A, mean_B
def decide_ab(successes_A, trials_A, successes_B, trials_B,
probe_threshold=0.80, probe_rel_improve=0.10,
strong_threshold=0.95, strong_rel_improve=0.15,
immediate_fail_prob=0.05, immediate_drop_rel=0.10,
alpha0=2.0, beta0=2.0, n_samples=20000, seed=42):
posterior_prob, mean_A, mean_B = posterior_prob_B_gt_A(
successes_A, trials_A, successes_B, trials_B, alpha0, beta0, n_samples, seed
)
rel_improve = (mean_B / mean_A - 1) if mean_A > 0 else float('inf')
if posterior_prob >= strong_threshold and rel_improve >= strong_rel_improve:
decision = "STRONG_PASS (full rollout)"
elif posterior_prob >= probe_threshold and rel_improve >= probe_rel_improve:
decision = "PROBE_PASS (increase traffic)"
elif posterior_prob <= immediate_fail_prob and rel_improve <= -immediate_drop_rel:
decision = "IMMEDIATE_FAIL (rollback)"
else:
decision = "UNCERTAIN (extend or collect more data)"
return {
"posterior_prob_B_gt_A": posterior_prob,
"mean_A": mean_A,
"mean_B": mean_B,
"relative_improvement": rel_improve,
"decision": decision
}
In [121]:
# Cell 10 (替换版) — 小流量 A/B 实验模拟并显示结果
# 假设已在之前的 cell 定义了 decide_ab(...)
# 可修改 succ/trials 数值做多次实验
# 示例观测数据(可编辑)
succ_A = 120 # 控制组成功数(如完成观看/互动数)
trials_A = 4000 # 控制组展示数
succ_B = 150 # 变体组成功数
trials_B = 4000 # 变体组展示数
# 调用判定
result = decide_ab(succ_A, trials_A, succ_B, trials_B,
probe_threshold=0.80, probe_rel_improve=0.10,
strong_threshold=0.95, strong_rel_improve=0.15,
immediate_fail_prob=0.05, immediate_drop_rel=0.10,
alpha0=2.0, beta0=2.0, n_samples=30000, seed=123)
# 输出结果
from IPython.display import Markdown, display
display(Markdown("## A/B 判定结果示例"))
import json
print(json.dumps(result, ensure_ascii=False, indent=2))
A/B 判定结果示例¶
{
"posterior_prob_B_gt_A": 0.9682666666666667,
"mean_A": 0.030469530469530468,
"mean_B": 0.03796203796203796,
"relative_improvement": 0.24590163934426235,
"decision": "STRONG_PASS (full rollout)"
}
In [122]:
# Cell 11
def simple_replay(material, metrics):
# naive importance: check which factor correlated; here we mock output
report = {"asset_id":"demo_asset_001","metrics":metrics,
"factor_contribution":[{"factor":"hook","impact":0.4},{"factor":"cover","impact":0.3},{"factor":"cta","impact":0.3}],
"actions":[{"action":"保留hook变体","confidence":0.8},{"action":"替换封面尝试AIGC","confidence":0.6}]}
return report
# mock metrics after trial
metrics = {"views":5000,"completion_rate":0.32,"clicks":120,"conversions":12}
replay_report = simple_replay(material, metrics)
save_json(replay_report, "replay_report.json")
show(replay_report, "Replay Report (module H)")
Saved /Users/vallenliu/demo_outputs/replay_report.json
Replay Report (module H)¶
{
"asset_id": "demo_asset_001",
"metrics": {
"views": 5000,
"completion_rate": 0.32,
"clicks": 120,
"conversions": 12
},
"factor_contribution": [
{
"factor": "hook",
"impact": 0.4
},
{
"factor": "cover",
"impact": 0.3
},
{
"factor": "cta",
"impact": 0.3
}
],
"actions": [
{
"action": "保留hook变体",
"confidence": 0.8
},
{
"action": "替换封面尝试AIGC",
"confidence": 0.6
}
]
}
In [123]:
# Cell 12
def gen_business_package(koc_profile, candidate, replay_report):
pkg = {"koc_id":koc_profile["id"],"candidate_id":candidate["id"],
"summary":{"expected_reach":5000,"expected_conversion_range":[10,30]},
"cases":[{"case":"相似KOC转化3.2%","link":""}],
"price_suggestion":{"type":"cpm_or_cpa","suggested_min":500},
"notes":"含样品/流量包建议"}
return pkg
business_package = gen_business_package(koc_profile, candidate, replay_report)
save_json(business_package, "business_package.json")
show(business_package, "Business Package (module I)")
Saved /Users/vallenliu/demo_outputs/business_package.json
Business Package (module I)¶
{
"koc_id": "koc_demo_001",
"candidate_id": "sku123",
"summary": {
"expected_reach": 5000,
"expected_conversion_range": [
10,
30
]
},
"cases": [
{
"case": "相似KOC转化3.2%",
"link": ""
}
],
"price_suggestion": {
"type": "cpm_or_cpa",
"suggested_min": 500
},
"notes": "含样品/流量包建议"
}
In [124]:
# Cell 13
from datetime import datetime, timezone
def compliance_check(texts, assets):
# naive rules: block if words contain '治愈' 或 '保证'
blockers = ["治愈", "保证"]
issues = []
for t in texts:
for b in blockers:
if b in t:
issues.append({"type": "blocker", "text": t, "reason": f"包含敏感词 {b}"})
status = "pass" if not issues else "require_approval"
return {"status": status, "issues": issues}
texts_to_check = [s["body"] for s in scripts]
audit = compliance_check(texts_to_check, material)
audit_record = {
"asset": "demo_asset_001",
"audit": audit,
"timestamp": datetime.now(timezone.utc).isoformat()
}
save_json(audit_record, "audit.json")
show(audit_record, "Compliance Audit (module J)")
Saved /Users/vallenliu/demo_outputs/audit.json
Compliance Audit (module J)¶
{
"asset": "demo_asset_001",
"audit": {
"status": "pass",
"issues": []
},
"timestamp": "2026-04-25T02:35:53.322577+00:00"
}
In [125]:
# Cell 14 的内容(带时区)
from datetime import datetime, timezone
final_package = {
"meta": {"generated_at": datetime.now(timezone.utc).isoformat()},
"module_A_koc_profile": koc_profile,
"module_B_audience_profile": aud_profile,
"module_C_candidates": candidates_list,
"module_D_scripts": scripts,
"module_E_material": material,
"module_F_publish_plan": publish_plan,
"module_G_activity": activity,
"module_H_replay": replay_report,
"module_I_business_package": business_package,
"module_J_audit": audit_record
}
save_json(final_package, "final_package.json")
show(final_package, "Final Package (all modules)")
Saved /Users/vallenliu/demo_outputs/final_package.json
Final Package (all modules)¶
{
"meta": {
"generated_at": "2026-04-25T02:35:53.824806+00:00"
},
"module_A_koc_profile": {
"id": "koc_demo_001",
"name": "Demo KOC",
"style_vector": {
"Humor": {
"val": 45.0,
"conf": 0.5
},
"Emotionality": {
"val": 50.0,
"conf": 0.5
},
"Rationality": {
"val": 26.7,
"conf": 0.5
},
"Pace": {
"val": 58.3,
"conf": 0.5
},
"Spontaneity": {
"val": 71.7,
"conf": 0.5
}
},
"capability": {
"shoot": 60,
"edit": 50,
"improv": 40
},
"preferences": {
"categories": [
"护肤",
"美妆"
],
"price_range": "low"
},
"consent_flags": {
"orders": false,
"followers": false
},
"created_at": "2026-04-25T02:35:47.882233+00:00"
},
"module_B_audience_profile": {
"koc_id": "koc_demo_001",
"segments": [
{
"id": "s_demo",
"label": "默认年轻女性",
"age_range": [
18,
34
],
"gender": "F",
"interest_tags": [
"护肤",
"美妆"
],
"conf": 0.45,
"sample_size": 80
}
],
"suggested_test_plan": "Run 2 variants in private group, 500 impressions each"
},
"module_C_candidates": [
{
"id": "sku123",
"name": "护肤X霜(敏感型)",
"brand": "小众牌A",
"price": 79.0,
"source_tag": "护肤",
"evidence": [
{
"type": "ecom_trend",
"text": "近30天搜索↑45%",
"url": ""
}
],
"supply_signals": {
"can_sample": true,
"sample_time_days": 5,
"inventory": 800,
"lead_time_days": 3
},
"margin_pct": 0.35,
"brand_score": 78,
"supply_score": 65.9,
"audience_match_score": 100.0,
"rank_score": 80.6
}
],
"module_D_scripts": [
{
"id": "scr_sku123_0",
"hook": "最近用了这个,效果挺让我惊喜。",
"body": "产品:护肤X霜(敏感型),价格79.0元,卖点:近30天搜索↑45%",
"cta": "点击链接了解/私信拿优惠",
"style_label": "温和可信",
"target_segment": "s_demo",
"evidence": [
{
"type": "ecom_trend",
"text": "近30天搜索↑45%",
"url": ""
}
]
},
{
"id": "scr_sku123_1",
"hook": "性价比超高,值得一试。",
"body": "产品:护肤X霜(敏感型),价格79.0元,卖点:近30天搜索↑45%",
"cta": "点击链接了解/私信拿优惠",
"style_label": "中性推荐",
"target_segment": "s_demo",
"evidence": [
{
"type": "ecom_trend",
"text": "近30天搜索↑45%",
"url": ""
}
]
},
{
"id": "scr_sku123_2",
"hook": "真心推荐,尤其适合你们。",
"body": "产品:护肤X霜(敏感型),价格79.0元,卖点:近30天搜索↑45%",
"cta": "点击链接了解/私信拿优惠",
"style_label": "情绪化种草",
"target_segment": "s_demo",
"evidence": [
{
"type": "ecom_trend",
"text": "近30天搜索↑45%",
"url": ""
}
]
}
],
"module_E_material": {
"storyboard": [
{
"start": 0,
"end": 20,
"action": "钩子",
"camera": "近景",
"dialogue": "第1段示例台词",
"cues": "表情"
},
{
"start": 20,
"end": 40,
"action": "核心卖点",
"camera": "中景",
"dialogue": "第2段示例台词",
"cues": "展示细节"
},
{
"start": 40,
"end": 60,
"action": "结尾CTA",
"camera": "特写",
"dialogue": "第3段示例台词",
"cues": "强调CTA"
}
],
"subtitles": [
{
"index": 1,
"start": "00:00:00",
"end": "00:00:20",
"text": "第1段示例台词"
},
{
"index": 2,
"start": "00:00:20",
"end": "00:00:40",
"text": "第2段示例台词"
},
{
"index": 3,
"start": "00:00:40",
"end": "00:00:60",
"text": "第3段示例台词"
}
],
"cut_list": [
{
"start": 0,
"end": 20,
"reason": "钩子",
"replace_with": "b-roll_closeup"
},
{
"start": 20,
"end": 40,
"reason": "核心卖点",
"replace_with": "b-roll_closeup"
},
{
"start": 40,
"end": 60,
"reason": "结尾CTA",
"replace_with": "b-roll_closeup"
}
],
"makeup_plan": {
"level": "推荐",
"steps": [
"清洁",
"保湿",
"轻薄粉底",
"自然腮红"
],
"color_palette": [
"#F2C1C1",
"#FFF6EA"
],
"confidence": 0.78
},
"covers": [
{
"url": "cover1.png",
"prompt": "面部特写+大字钩子",
"ctr_score": 0.12,
"uncertainty": 0.03
}
],
"variants": [
{
"id": "v1",
"desc": "快节奏钩子"
},
{
"id": "v2",
"desc": "慢节奏讲解"
}
]
},
"module_F_publish_plan": {
"primary_time": "2026-04-25T20:00:50.871893+00:00",
"backup_time": "2026-04-25T12:00:50.871893+00:00",
"channel": "视频号",
"frequency_per_week": 3,
"tags": [
"护肤",
"敏感肌"
],
"reason": "目标受众晚间活跃,历史相似案例完成率高"
},
"module_G_activity": {
"activity_id": "act_1777052783",
"type": "reply_lottery",
"title": "评论抽奖-试用",
"trigger": {
"time_window_hours": 1,
"keyword": "#我想试",
"start_time": "2026-04-25T01:46:23.125706"
},
"reward": {
"type": "sample",
"quantity": 5
},
"eligibility": {
"min_followers": 0
},
"automation": {
"auto_collect": true
},
"status": "active"
},
"module_H_replay": {
"asset_id": "demo_asset_001",
"metrics": {
"views": 5000,
"completion_rate": 0.32,
"clicks": 120,
"conversions": 12
},
"factor_contribution": [
{
"factor": "hook",
"impact": 0.4
},
{
"factor": "cover",
"impact": 0.3
},
{
"factor": "cta",
"impact": 0.3
}
],
"actions": [
{
"action": "保留hook变体",
"confidence": 0.8
},
{
"action": "替换封面尝试AIGC",
"confidence": 0.6
}
]
},
"module_I_business_package": {
"koc_id": "koc_demo_001",
"candidate_id": "sku123",
"summary": {
"expected_reach": 5000,
"expected_conversion_range": [
10,
30
]
},
"cases": [
{
"case": "相似KOC转化3.2%",
"link": ""
}
],
"price_suggestion": {
"type": "cpm_or_cpa",
"suggested_min": 500
},
"notes": "含样品/流量包建议"
},
"module_J_audit": {
"asset": "demo_asset_001",
"audit": {
"status": "pass",
"issues": []
},
"timestamp": "2026-04-25T02:35:53.322577+00:00"
}
}
In [126]:
# Cell 15
display(Markdown("### 输出文件列表(demo_outputs 目录)"))
for p in sorted(OUT_DIR.iterdir()):
display(Markdown(f"- {p.name} ({p.stat().st_size} bytes)"))
输出文件列表(demo_outputs 目录)¶
- activity.json (398 bytes)
- activity.json.bak (398 bytes)
- audience_profile.json (391 bytes)
- audience_profile.json.bak (391 bytes)
- audit.json (139 bytes)
- audit.json.bak (139 bytes)
- business_package.json (370 bytes)
- business_package.json.bak (370 bytes)
- candidates_list.json (614 bytes)
- candidates_list.json.bak (614 bytes)
- conversion_report.json (1307 bytes)
- conversion_report.json.bak (1126 bytes)
- final_package.json (7426 bytes)
- final_package.json.bak (7426 bytes)
- koc_profile.json (687 bytes)
- koc_profile.json.bak (687 bytes)
- material_package.json (1814 bytes)
- material_package.json.bak (1814 bytes)
- product_candidate.json (409 bytes)
- product_candidate.json.bak (409 bytes)
- publish_plan.json (282 bytes)
- publish_plan.json.bak (282 bytes)
- questionnaire.json (6873 bytes)
- questionnaire.json.bak (6873 bytes)
- recheck_encoding.json (1488 bytes)
- recheck_encoding.json.bak (1285 bytes)
- replay_report.json (516 bytes)
- replay_report.json.bak (516 bytes)
- scripts.json (1407 bytes)
- scripts.json.bak (1407 bytes)
In [127]:
from pathlib import Path
OUT_DIR = Path("demo_outputs")
if not OUT_DIR.exists():
raise SystemExit(f"Directory not found: {OUT_DIR.resolve()}")
json_files = sorted([p for p in OUT_DIR.iterdir() if p.suffix.lower()=='.json'])
print("Found json files:")
for p in json_files:
print("-", p.name)
Found json files: - activity.json - audience_profile.json - audit.json - business_package.json - candidates_list.json - conversion_report.json - final_package.json - koc_profile.json - material_package.json - product_candidate.json - publish_plan.json - questionnaire.json - recheck_encoding.json - replay_report.json - scripts.json
In [ ]:
# Cell 16
import chardet
from shutil import copy2
det_results = {}
for p in json_files:
raw = p.read_bytes()
det = chardet.detect(raw)
det_results[p.name] = det
bak = p.with_suffix(p.suffix + ".bak")
if not bak.exists():
copy2(p, bak) # binary copy
print(f"Backup created: {bak.name}")
else:
print(f"Backup exists: {bak.name}")
print("\nDetection results:")
for name, d in det_results.items():
print(name, "->", d)
In [ ]:
# Cell 17
def try_convert_to_utf8(p: Path, detected_encoding=None):
candidates = []
if detected_encoding:
candidates.append(detected_encoding)
# add common fallbacks
candidates.extend(["utf-8", "gbk", "gb2312", "latin1", "iso-8859-1"])
tried = set()
for enc in candidates:
if not enc or enc in tried:
continue
tried.add(enc)
try:
text = p.read_text(encoding=enc, errors="strict")
# if successful, write back as UTF-8
p.write_text(text, encoding="utf-8")
return {"status":"converted","used_encoding":enc}
except Exception as e:
# try next
last_error = e
# as last resort, read with replace and write utf-8 (preserves content but may lose chars)
text = p.read_text(encoding=candidates[-1], errors="replace")
p.write_text(text, encoding="utf-8")
return {"status":"converted_with_replace","used_encoding":candidates[-1], "note":str(last_error)}
In [ ]:
# Cell 18
conversion_report = {}
for p in json_files:
det = det_results.get(p.name, {})
enc = det.get("encoding")
print(f"Converting {p.name}, detected: {det}")
res = try_convert_to_utf8(p, detected_encoding=enc)
conversion_report[p.name] = res
print(" ->", res)
save_report = OUT_DIR / "conversion_report.json"
save_report.write_text(__import__("json").dumps(conversion_report, ensure_ascii=False, indent=2), encoding="utf-8")
print("Saved conversion_report.json")
In [ ]:
# Cell 19
recheck = {}
for p in json_files:
raw = p.read_bytes()
det = chardet.detect(raw)
recheck[p.name] = det
print("Re-detection results after conversion:")
for name, d in recheck.items():
print(name, "->", d)
# save recheck
(OUT_DIR / "recheck_encoding.json").write_text(__import__("json").dumps(recheck, ensure_ascii=False, indent=2), encoding="utf-8")
In [ ]:
# Cell 20
import zipfile
zip_name = Path("demo_outputs.zip")
with zipfile.ZipFile(zip_name, "w", zipfile.ZIP_DEFLATED) as z:
for p in sorted(OUT_DIR.iterdir()):
# skip the zip if exists in same dir to avoid recursion
if p == zip_name:
continue
z.write(p, arcname=p.name)
print("Created zip:", zip_name.resolve())
In [ ]:
# Cell 21
import json
print("det_results:")
print(json.dumps(__import__("json").loads((OUT_DIR/"conversion_report.json").read_text(encoding="utf-8")), ensure_ascii=False, indent=2))
# 以及 recheck
print("recheck:")
print(json.dumps(__import__("json").loads((OUT_DIR/"recheck_encoding.json").read_text(encoding="utf-8")), ensure_ascii=False, indent=2))
In [ ]:
# Cell 22
p = Path("demo_outputs/final_package.json") # 改为你遇问题的文件名
raw = p.read_bytes()
print("first 200 raw bytes:", raw[:200])
# 尝试用不同编码查看前 500 字符(不会写回)
for enc in ["utf-8","gbk","gb2312","latin1","iso-8859-1"]:
try:
print(f"\n--- try {enc} ---")
print(p.read_text(encoding=enc)[:500])
except Exception as e:
print(f"fail {enc}: {e}")
In [ ]:
# Cell 23
bak = Path("demo_outputs/final_package.json.bak")
print("bak exists:", bak.exists(), "size:", bak.stat().st_size if bak.exists() else None)
print("orig size:", p.stat().st_size)
In [ ]:
# Cell 24
# 强制安全转换(已备份前提)
text = p.read_bytes().decode("latin1", errors="replace") # latin1 保证不会抛异常
p.write_text(text, encoding="utf-8")
print("force-converted using latin1->utf-8 (with replace).")
In [ ]:
# Cell 25
from pathlib import Path
import json # Import json as a separate module
p = Path("demo_outputs/final_package.json")
print("size:", p.stat().st_size)
print(json.dumps(json.loads(p.read_text(encoding="utf-8")), ensure_ascii=False, indent=2)[:1000])
In [ ]:
# Cell 26
import zipfile, pathlib
OUT_DIR = pathlib.Path("demo_outputs")
zip_name = pathlib.Path("demo_outputs.zip")
with zipfile.ZipFile(zip_name, "w", zipfile.ZIP_DEFLATED) as z:
for f in sorted(OUT_DIR.iterdir()):
if f == zip_name: continue
z.write(f, arcname=f.name)
print("zip created:", zip_name.resolve())
In [ ]:
# Cell 27
display(Markdown("""
## 说明:如何把这个 Demo 分享或在网页上呈现
- 你现在有 demo_outputs/*.json 与 demo_outputs.zip。上传这些文件与本 Notebook 到 GitHub 后:
- 评审可以直接在 GitHub 上查看 JSON 文件;或
- 使用 Binder(https://mybinder.org/)或 Google Colab 打开 Notebook 实时运行交互演示。
- 若要把 Demo 做成静态前端页面(可供非技术评审直接交互):
1. 使用之前给你的 web/index.html(示例)读取 demo_outputs/*.json 并渲染页面(前端无需后端支持)。
2. 将 web/ 与 demo_outputs/ 放到 GitHub 仓库并启用 GitHub Pages(或部署到 Vercel/Netlify),浏览器即可访问静态演示页。
- 若要做成真实产品演示(支持真实 LLM/ASR/图像生成与发布):
- 需要把 Notebook 中的模拟函数替换为后端服务(FastAPI)并接入实际模型与外部 API(注意合规与授权)。
"""))
In [ ]:
# Cell 28
display(Markdown("""
### Demo 完成 —— 下一步建议(选项)
1. 我可以把本 Notebook 与 web/index.html、必要静态 JSON 打包为 GitHub 仓库模板(你可直接 clone)。
2. 我可以把某些模块的模拟函数替换为真实 API 调用示例(例如:调用 OpenAI 生成分镜、调用 Whisper 做 ASR,或调用 SD 做封面图)。
3. 我可以生成一个简单的静态前端(读取 demo_outputs/final_package.json)并部署到 GitHub Pages,供非技术评审直接访问。
告诉我你想要哪一项,我马上为你生成对应的文件/部署步骤。
"""))
In [ ]: