Compare commits
557 Commits
codex/fix-
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76a89a7098 | ||
|
|
4b0a331d98 | ||
|
|
bed4488a72 | ||
|
|
9d84cbfd43 | ||
|
|
4c59b74ced | ||
|
|
9ca8d4e43c | ||
|
|
6d6f3b473f | ||
|
|
8fb908cf4a | ||
|
|
89407b054f | ||
|
|
8145c227c7 | ||
|
|
c83fb4cfa9 | ||
|
|
ba5fe06b13 | ||
|
|
37f1802274 | ||
|
|
bd942e9427 | ||
|
|
ee50e440ce | ||
|
|
9bafa73ffc | ||
|
|
56cd883148 | ||
|
|
08532b2987 | ||
|
|
3c0e558fbe | ||
|
|
01c73e02a2 | ||
|
|
15010ab724 | ||
|
|
7a2520dc67 | ||
|
|
4d1a664678 | ||
|
|
11b9ea6fef | ||
|
|
eb521fd6d8 | ||
|
|
32b7071ab6 | ||
|
|
239b773288 | ||
|
|
56ebba045b | ||
|
|
cac9752aac | ||
|
|
b7cb807683 | ||
|
|
a3ace326c8 | ||
|
|
df6714c3f7 | ||
|
|
9260cc1740 | ||
|
|
01cc027622 | ||
|
|
4683b58f91 | ||
|
|
55e14c0332 | ||
|
|
8fa95b94a9 | ||
|
|
d6d8777e41 | ||
|
|
2efe9bd931 | ||
|
|
400133fec1 | ||
|
|
9be44d27b1 | ||
|
|
d9a13474cb | ||
|
|
c764abc7fc | ||
|
|
93065a58e4 | ||
|
|
bb8c29e56d | ||
|
|
58302c9fa7 | ||
|
|
5347c95b97 | ||
|
|
1dd4181fab | ||
|
|
a1e56a2c6b | ||
|
|
133232e2f4 | ||
|
|
35ad29b04d | ||
|
|
bfb8e3c99d | ||
|
|
69fa191097 | ||
|
|
03d60c202f | ||
|
|
3f528dade3 | ||
|
|
5f7073798a | ||
|
|
3b9448b776 | ||
|
|
58e8d5ee0f | ||
|
|
b2604a576d | ||
|
|
facf53b925 | ||
|
|
e78b2720d9 | ||
|
|
930ad402ff | ||
|
|
6134b8e332 | ||
|
|
01119c2c82 | ||
|
|
a32982395e | ||
|
|
97e7e2843b | ||
|
|
d90f96fab3 | ||
|
|
462e354a71 | ||
|
|
6cf2d23521 | ||
|
|
3ca0f742f7 | ||
|
|
94e1967171 | ||
|
|
07db301c54 | ||
|
|
42b1c25418 | ||
|
|
f232639c99 | ||
|
|
7a26bfa388 | ||
|
|
66f2c50964 | ||
|
|
24597dcebb | ||
|
|
e820fa2e80 | ||
|
|
47d5c8043b | ||
|
|
e0ed5cc732 | ||
|
|
e68c6c22d1 | ||
|
|
7cb523be18 | ||
|
|
339bf68e14 | ||
|
|
12dc452061 | ||
|
|
f10d73ff83 | ||
|
|
e5ecf5512e | ||
|
|
e4534c2f96 | ||
|
|
83b813c1b9 | ||
|
|
35b9d0f0c5 | ||
|
|
4bc271f7ed | ||
|
|
7e057435c5 | ||
|
|
c0a7a55b72 | ||
|
|
da546dd8d5 | ||
|
|
12c8c7e94d | ||
|
|
6e70a6eb47 | ||
|
|
dcdef76f06 | ||
|
|
5e1428c887 | ||
|
|
2a11ba26a7 | ||
|
|
f3c37c471d | ||
|
|
500cccf14d | ||
|
|
d03db3c015 | ||
|
|
0fed025c58 | ||
|
|
4557533c52 | ||
|
|
0304f666d4 | ||
|
|
3a045fddfc | ||
|
|
b9b3a410ff | ||
|
|
3c92ebf34b | ||
|
|
98d91f2e3f | ||
|
|
87d47f171c | ||
|
|
915b8d061d | ||
|
|
41bd6c6e77 | ||
|
|
81062b624b | ||
|
|
bf2888b0fb | ||
|
|
dd6b6160fe | ||
|
|
371a2b325e | ||
|
|
11896c24dc | ||
|
|
c91d20f26b | ||
|
|
ea1043ef7c | ||
|
|
353465d38a | ||
|
|
f3a3cfe52a | ||
|
|
89d8c454bb | ||
|
|
73d2d863e5 | ||
|
|
7cf0235ac7 | ||
|
|
af0b49a819 | ||
|
|
593e87b175 | ||
|
|
72ded9e1e5 | ||
|
|
b347aa44b9 | ||
|
|
58ac16781c | ||
|
|
df7400c6cd | ||
|
|
b7838b382f | ||
|
|
46aa89ddfa | ||
|
|
2e8f57dfbe | ||
|
|
8f10930cca | ||
|
|
64b4ec39ec | ||
|
|
9843843fbb | ||
|
|
b109bbf3c4 | ||
|
|
d1a08b0b37 | ||
|
|
cef27c77df | ||
|
|
c196a0e228 | ||
|
|
06e196a282 | ||
|
|
d98f24e8eb | ||
|
|
291cdf019f | ||
|
|
998e0da9b4 | ||
|
|
c1b2fa82f5 | ||
|
|
81701e85f4 | ||
|
|
afa91755c1 | ||
|
|
e4de72d7d7 | ||
|
|
f2aa81815e | ||
|
|
64a39a89b3 | ||
|
|
e6b48cefa8 | ||
|
|
50daad84a7 | ||
|
|
630d4c73bc | ||
|
|
6c5854bb5f | ||
|
|
bd33f7ff60 | ||
|
|
dab782cc6d | ||
|
|
a00f34ce87 | ||
|
|
44ef5a70a1 | ||
|
|
e3dadc28db | ||
|
|
aad26ea87c | ||
|
|
ac5539a8ef | ||
|
|
35f5f87618 | ||
|
|
0ade55469e | ||
|
|
e1c9499c1c | ||
|
|
5c0ff7f8cf | ||
|
|
7e29c00eb8 | ||
|
|
61816c90e8 | ||
|
|
3fd349c830 | ||
|
|
f3a199434f | ||
|
|
da7bc88b5e | ||
|
|
b877805a4c | ||
|
|
19d960a79d | ||
|
|
8a75d34276 | ||
|
|
4a43a3075a | ||
|
|
92fac19b87 | ||
|
|
aa8fdd109f | ||
|
|
c3130b28dd | ||
|
|
4f4ee82277 | ||
|
|
238c626bcb | ||
|
|
0f7ad3e036 | ||
|
|
916ddc010b | ||
|
|
7b784d6819 | ||
|
|
b297491efe | ||
|
|
bc48926cc5 | ||
|
|
2381af5ee0 | ||
|
|
c0a06e1798 | ||
|
|
05d6eadf1b | ||
|
|
b0a267823a | ||
|
|
98cd401f88 | ||
|
|
2ca3559df2 | ||
|
|
a4aa796114 | ||
|
|
9a96e9500b | ||
|
|
ac575a6499 | ||
|
|
a726f012d5 | ||
|
|
04acb3079d | ||
|
|
97e33bf1e0 | ||
|
|
90eaf82d02 | ||
|
|
714a5716db | ||
|
|
4238d0f87b | ||
|
|
d446ba0f2f | ||
|
|
8340df8cb7 | ||
|
|
3bbd4c15ba | ||
|
|
dcabebbcf2 | ||
|
|
0fff09a15c | ||
|
|
0709392d9d | ||
|
|
82b5616921 | ||
|
|
f950c28169 | ||
|
|
bfd5b213b2 | ||
|
|
5d4927c119 | ||
|
|
5aa2412dee | ||
|
|
710f7216d0 | ||
|
|
9fd8ac2886 | ||
|
|
f6f9bf574c | ||
|
|
48c5db3b85 | ||
|
|
c5db3eb0da | ||
|
|
bfc5dac624 | ||
|
|
ef9c2272b9 | ||
|
|
1eef91ec7a | ||
|
|
d522c07b39 | ||
|
|
105178e50b | ||
|
|
a1df2bc8d5 | ||
|
|
051c654713 | ||
|
|
d650b10797 | ||
|
|
1f2e8b1e84 | ||
|
|
b389a50e07 | ||
|
|
0251835999 | ||
|
|
5ae28083d9 | ||
|
|
1717dad3d7 | ||
|
|
0208c014d2 | ||
|
|
868d75f972 | ||
|
|
1487f7fb10 | ||
|
|
6d22d4920e | ||
|
|
bf88fcd48e | ||
|
|
2f1ba21352 | ||
|
|
0d81839447 | ||
|
|
0406a62611 | ||
|
|
7090f08dba | ||
|
|
353e565e52 | ||
|
|
30e30ac7b6 | ||
|
|
4f64934818 | ||
|
|
ae0f1e5a81 | ||
|
|
b73dc6df3f | ||
|
|
1b94177828 | ||
|
|
6ac412716a | ||
|
|
74e19400bd | ||
|
|
8d9984dc04 | ||
|
|
5ea556d406 | ||
|
|
469a5845e8 | ||
|
|
bc8afd026c | ||
|
|
65d38d4632 | ||
|
|
717231167f | ||
|
|
768127ac52 | ||
|
|
1272c01ec6 | ||
|
|
b2dbf98c3b | ||
|
|
56f787351a | ||
|
|
d66d29c3ee | ||
|
|
bbf21b81af | ||
|
|
8b4bcdf277 | ||
|
|
4622be3441 | ||
|
|
8cc197aba2 | ||
|
|
01c888c565 | ||
|
|
68f9e051f4 | ||
|
|
af48452fd6 | ||
|
|
4da4a07a78 | ||
|
|
6d4b188787 | ||
|
|
0538c8d5f6 | ||
|
|
d2dea022ea | ||
|
|
4ae3a39970 | ||
|
|
b7b12829f9 | ||
|
|
b6de73a4a1 | ||
|
|
2ae377dadf | ||
|
|
63470b69d3 | ||
|
|
05643de83a | ||
|
|
5a5f268358 | ||
|
|
31f88898c2 | ||
|
|
0cea70890a | ||
|
|
106c1935f4 | ||
|
|
9ada32477c | ||
|
|
c522bfc5f6 | ||
|
|
c7fdd57156 | ||
|
|
e3da4ffbb3 | ||
|
|
de10712586 | ||
|
|
5e2bdd5b77 | ||
|
|
3ec5fa9cb5 | ||
|
|
690cafb5a1 | ||
|
|
4174c90ab0 | ||
|
|
52790f3f6d | ||
|
|
1f0d37a1fe | ||
|
|
1c4fcae5ca | ||
|
|
1bdb0f2bd8 | ||
|
|
c016200bf4 | ||
|
|
60028cdbd3 | ||
|
|
78b6f156ba | ||
|
|
c329d96dff | ||
|
|
8e4d7d306e | ||
|
|
00a808518e | ||
|
|
d6ae216c8c | ||
|
|
e57793829c | ||
|
|
fb5a4435d7 | ||
|
|
e67c926ac9 | ||
|
|
d5b41ec11b | ||
|
|
2efec5fb15 | ||
|
|
f4ec50cd30 | ||
|
|
958a6d9829 | ||
|
|
8bffd0307d | ||
|
|
73ec78e8ab | ||
|
|
e57ad01988 | ||
|
|
88101cf5d0 | ||
|
|
b272abccca | ||
|
|
a4fcf3d3c1 | ||
|
|
1f7d8cfaf7 | ||
|
|
09951b02ed | ||
|
|
7441c7751b | ||
|
|
5d38735548 | ||
|
|
d030e4cf22 | ||
|
|
cf4d8aedea | ||
|
|
a46396ca7f | ||
|
|
30951af04c | ||
|
|
1bb8900f26 | ||
|
|
e506251194 | ||
|
|
8edd8a8604 | ||
|
|
67cd35e2de | ||
|
|
e7ab333c58 | ||
|
|
193b6e53c5 | ||
|
|
48e46e35c0 | ||
|
|
760c9c0a5e | ||
|
|
506fed4ba3 | ||
|
|
2cc08684ec | ||
|
|
43f340c080 | ||
|
|
3f070a668b | ||
|
|
255140224b | ||
|
|
c0ec430587 | ||
|
|
b636303481 | ||
|
|
9c45a87723 | ||
|
|
396a51dd7f | ||
|
|
0facbcd8cf | ||
|
|
3f2830dab5 | ||
|
|
b68125ca26 | ||
|
|
2c47a79f05 | ||
|
|
875810620f | ||
|
|
e4f3c8fc5e | ||
|
|
3cfc82925e | ||
|
|
6f454821a7 | ||
|
|
a4590db8ba | ||
|
|
4d62731730 | ||
|
|
854e13201f | ||
|
|
ca27f2e4eb | ||
|
|
7fa5dc72bf | ||
|
|
e8e4e0f94f | ||
|
|
8d791ef50b | ||
|
|
a1818da1f3 | ||
|
|
6bf05ec2f1 | ||
|
|
c2528c1339 | ||
|
|
44b8075506 | ||
|
|
38085d4cec | ||
|
|
93f1a558a5 | ||
|
|
f7772cc465 | ||
|
|
5b9f712abe | ||
|
|
190695e546 | ||
|
|
bd47115e27 | ||
|
|
296269bd43 | ||
|
|
8c3ca03f92 | ||
|
|
45e1aad308 | ||
|
|
756b01af66 | ||
|
|
50af4be9a8 | ||
|
|
0242aebb66 | ||
|
|
ac93d185f4 | ||
|
|
3090b18fa4 | ||
|
|
c9a5478fef | ||
|
|
c9aa2efd8b | ||
|
|
b2f9ccf4f0 | ||
|
|
c9d2ff330f | ||
|
|
51daa86735 | ||
|
|
9ba1d8aedd | ||
|
|
2bf2245cea | ||
|
|
baf5eb2881 | ||
|
|
cd13044849 | ||
|
|
36c177762f | ||
|
|
89c2bc96e4 | ||
|
|
124ddc7376 | ||
|
|
108b05cc8b | ||
|
|
23ddd3ede9 | ||
|
|
809bcbbf67 | ||
|
|
91601da3f8 | ||
|
|
645cd397c2 | ||
|
|
d20615992a | ||
|
|
032ba2bbd3 | ||
|
|
205b9ea3f6 | ||
|
|
6fc064cd9a | ||
|
|
3e66a71c8c | ||
|
|
d88d9b7326 | ||
|
|
cc3be7fd4f | ||
|
|
2e5ff091fa | ||
|
|
0fc96837f4 | ||
|
|
880d15b055 | ||
|
|
840cb0acdb | ||
|
|
350022f2ea | ||
|
|
40ddf4eee0 | ||
|
|
f8222006b8 | ||
|
|
f911f96d1a | ||
|
|
dacf5f305a | ||
|
|
ebddd4e063 | ||
|
|
ec3b4d9157 | ||
|
|
03ab276032 | ||
|
|
482bfbdb64 | ||
|
|
91ebb88ac4 | ||
|
|
2c02e8d691 | ||
|
|
2a7916a73f | ||
|
|
d2e9867823 | ||
|
|
e3b4ed9d1e | ||
|
|
6461d2453f | ||
|
|
6622aa458b | ||
|
|
eb5558f1e7 | ||
|
|
2e7f2d8e94 | ||
|
|
786894f050 | ||
|
|
5e2186a808 | ||
|
|
8cd70d8326 | ||
|
|
8c13c941c0 | ||
|
|
aaebd6b86d | ||
|
|
d32b1f0e80 | ||
|
|
32013ae3b2 | ||
|
|
1911c1c53d | ||
|
|
45eb0d8464 | ||
|
|
75390f8495 | ||
|
|
6c548e2251 | ||
|
|
8fd6c4a42c | ||
|
|
dafde7e1a7 | ||
|
|
ec378258e7 | ||
|
|
02f9b49d4d | ||
|
|
2324c85e74 | ||
|
|
ac2f712354 | ||
|
|
f046f4c07f | ||
|
|
fd34e9e97a | ||
|
|
026d11b295 | ||
|
|
d504ddac51 | ||
|
|
934366bdd3 | ||
|
|
80bb654dbc | ||
|
|
6af0e3c77f | ||
|
|
995cb3fd6c | ||
|
|
1205ce87fb | ||
|
|
48dd9c5094 | ||
|
|
f288ef7031 | ||
|
|
1d3da03eee | ||
|
|
4a0a8bf75b | ||
|
|
16a1f22bd8 | ||
|
|
6788379d10 | ||
|
|
6fa425fb41 | ||
|
|
bdae154237 | ||
|
|
e7e045253d | ||
|
|
8d0c442bdd | ||
|
|
45ae7a3d88 | ||
|
|
04844099d9 | ||
|
|
2d4a3a4bf9 | ||
|
|
c55c74b999 | ||
|
|
1274dadfa1 | ||
|
|
21053aeb50 | ||
|
|
d2d6bcd263 | ||
|
|
6086f2e0f7 | ||
|
|
ffe0a0f512 | ||
|
|
8301f0ac7e | ||
|
|
98a0ca7af9 | ||
|
|
ac5d1a2d81 | ||
|
|
ac1bdb7812 | ||
|
|
e217865ccc | ||
|
|
c383a37f3f | ||
|
|
20d22b69ea | ||
|
|
cb0446e85f | ||
|
|
c0ba5e876a | ||
|
|
abe3be2ffa | ||
|
|
9ada594197 | ||
|
|
21cdbdb75c | ||
|
|
583e318295 | ||
|
|
595c88fa1e | ||
|
|
60bc8bf2e2 | ||
|
|
7572622cb5 | ||
|
|
4a6f6a2007 | ||
|
|
f6d34628f6 | ||
|
|
7a6afa3055 | ||
|
|
c08f76f315 | ||
|
|
83fd05d614 | ||
|
|
f221832735 | ||
|
|
e60707cdfb | ||
|
|
02682c81ed | ||
|
|
5908d1bdca | ||
|
|
2de9386a40 | ||
|
|
cd08dd7f97 | ||
|
|
dddafc579b | ||
|
|
774f1b4b45 | ||
|
|
f51dc173f7 | ||
|
|
1cf1fd01b1 | ||
|
|
aa8c2c7148 | ||
|
|
be0dd41472 | ||
|
|
e880c91028 | ||
|
|
0609194ef5 | ||
|
|
b8fe5f0c58 | ||
|
|
c4b92ce9f5 | ||
|
|
36e20216ed | ||
|
|
9a88b32e90 | ||
|
|
841443f37c | ||
|
|
48d71c711b | ||
|
|
bc900321f8 | ||
|
|
7e2f1ac671 | ||
|
|
96533a1c20 | ||
|
|
841db133d0 | ||
|
|
d946c1abab | ||
|
|
43773e871c | ||
|
|
9981bc9d22 | ||
|
|
9ed0805697 | ||
|
|
b5511e818f | ||
|
|
8f6b3a4b41 | ||
|
|
ebbf7bc063 | ||
|
|
f5b9f1bd74 | ||
|
|
18f8038b01 | ||
|
|
0fcc0ea265 | ||
|
|
1262017261 | ||
|
|
217aa4fd6d | ||
|
|
a3fb476eed | ||
|
|
d2d8dbab65 | ||
|
|
921e9eeb15 | ||
|
|
d990316d74 | ||
|
|
634d6ba457 | ||
|
|
160173a270 | ||
|
|
6f68178959 | ||
|
|
02339c93e0 | ||
|
|
07b76870c9 | ||
|
|
b8ca756090 | ||
|
|
45af902b63 | ||
|
|
a6059b5377 | ||
|
|
49f6b3ebd9 | ||
|
|
cb02cd350f | ||
|
|
bb6a862dbe | ||
|
|
c021945047 | ||
|
|
b361ca7723 | ||
|
|
f73a1710c4 | ||
|
|
c420d48263 | ||
|
|
3284b1cf82 | ||
|
|
d1ff323e50 | ||
|
|
4e71ed9ecd | ||
|
|
e167ba1497 | ||
|
|
f2aece5b71 | ||
|
|
d03a636baa | ||
|
|
81f4a0d18a | ||
|
|
3a779ca075 | ||
|
|
ebc6f2cfe5 | ||
|
|
cf52b07809 | ||
|
|
53e9a57baf | ||
|
|
32801b5d57 | ||
|
|
e868a66058 | ||
|
|
856075bb4d | ||
|
|
4736a7f7df | ||
|
|
ff49d31f73 | ||
|
|
de60792b7e | ||
|
|
a9b5385615 | ||
|
|
5690d79a40 | ||
|
|
14d645b4b1 | ||
|
|
f7726bea3f | ||
|
|
938b9fe963 | ||
|
|
7c37f066e8 |
@@ -5,8 +5,9 @@ set -u
|
||||
HOOK="$(cd "$(dirname "$0")/.." && pwd)/commit-quality.js"
|
||||
PASS=0; FAIL=0
|
||||
|
||||
# 真實格式 Telegram Token(測試字串,非活躍憑證)
|
||||
TOKEN='8610496165:AAFOlcWV4oRUSC2TI-fYux7JV97fjNzsYR8'
|
||||
# 真實格式 Telegram Token(測試字串,非活躍憑證);分段避免完整 token 形態入庫。
|
||||
TOKEN_PREFIX='8610496165:AAFOlcWV4o'
|
||||
TOKEN="${TOKEN_PREFIX}RUSC2TI-fYux7JV97fjNzsYR8"
|
||||
|
||||
run_case() {
|
||||
local name="$1"; local input="$2"; local expect="$3" # expect: allow|deny
|
||||
|
||||
201
.env.example
201
.env.example
@@ -47,6 +47,24 @@ EMAIL_RECEIVER=receiver_email@gmail.com
|
||||
PUBLIC_URL=http://your_server_ip:port
|
||||
NGROK_AUTH_TOKEN=your_ngrok_auth_token
|
||||
|
||||
# ==========================================
|
||||
# Webcrumbs 共用 UI Runtime
|
||||
# ==========================================
|
||||
# [預設 true] 啟用 momo-pro 全站 shell 載入自架 Webcrumbs runtime
|
||||
WEBCRUMBS_ENABLED=true
|
||||
# [預設] 共用 Webcrumbs/Open Design 工具入口;正式頁面載入不直接依賴此跨域 TLS
|
||||
WEBCRUMBS_BASE_URL=https://webcrumbs.wooo.work
|
||||
# [預設 shared-ui-poc-0.1.0] 生產環境必須固定版本,不使用官方 @latest
|
||||
WEBCRUMBS_RUNTIME_VERSION=shared-ui-poc-0.1.0
|
||||
# [預設] momo-pro 同源 asset proxy,避免跨域憑證/Basic Auth 影響正式頁面
|
||||
WEBCRUMBS_RUNTIME_PATH=/webcrumbs-assets/loader/webcrumbs-compatible-loader.js
|
||||
# [選填] 若實際 runtime URL 與 RUNTIME_PATH 不同,用此欄位直接覆寫
|
||||
WEBCRUMBS_RUNTIME_URL=
|
||||
# [預設] 共用 plugin base;各 plugin 應使用版本化子目錄
|
||||
WEBCRUMBS_PLUGIN_BASE_URL=/webcrumbs-assets/plugins
|
||||
# [預設] 188 user-space Shared UI Hub,只允許 proxy loader/plugins/demo
|
||||
WEBCRUMBS_ASSET_UPSTREAM_URL=http://192.168.0.188:18088
|
||||
|
||||
# ==========================================
|
||||
# Image / Release Tag(docker-compose*.yml)
|
||||
# ==========================================
|
||||
@@ -74,6 +92,7 @@ ALERT_WEBHOOK_PASSWORD=your_secure_webhook_password_here
|
||||
AUTO_FIX_ENABLED=true
|
||||
|
||||
# --- GitLab CI/CD ---
|
||||
GITLAB_ENABLED=false
|
||||
GITLAB_URL=http://192.168.0.110:8929
|
||||
GITLAB_TOKEN=your_gitlab_token_here
|
||||
GITLAB_PROJECT_ID=1
|
||||
@@ -97,6 +116,8 @@ GUNICORN_WORKER_CLASS=gthread
|
||||
GUNICORN_THREADS=4
|
||||
# [預設 300] 長查詢 / 報表匯出 timeout 秒數
|
||||
GUNICORN_TIMEOUT=300
|
||||
# [預設 false] worker 啟動時預熱 Dashboard 快取;正式環境通常維持 false
|
||||
DASHBOARD_PREWARM_ON_WORKER_INIT=false
|
||||
|
||||
# ==========================================
|
||||
# Database Settings
|
||||
@@ -108,12 +129,24 @@ POSTGRES_USER=momo
|
||||
POSTGRES_PASSWORD=your_secure_postgres_password_here
|
||||
POSTGRES_DB=momo_analytics
|
||||
|
||||
# Generic database URL / discrete keys used by tools and deployment scripts
|
||||
DATABASE_URL=postgresql://momo:your_secure_postgres_password_here@momo-postgres:5432/momo_analytics
|
||||
DATABASE_HOST=momo-postgres
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_USER=momo
|
||||
DATABASE_PASSWORD=your_secure_postgres_password_here
|
||||
DATABASE_DB=momo_analytics
|
||||
|
||||
# SQLite Configuration (Development/Backup)
|
||||
SQLITE_PATH=data/momo_database.db
|
||||
|
||||
# Database Type Selection (postgresql or sqlite)
|
||||
USE_POSTGRESQL=true
|
||||
|
||||
# Redis cache / queue optional endpoint
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
# ==========================================
|
||||
# Google Drive 自動匯入設定
|
||||
# ==========================================
|
||||
@@ -127,17 +160,51 @@ GDRIVE_FILE_PATTERN=即時業績_當日
|
||||
# ==========================================
|
||||
# Hermes 3 競價情報分析(Module 2 / ADR-012)
|
||||
# ==========================================
|
||||
# [選填] Hermes Ollama 端點;留空時自動走 GCP-A → GCP-B → 111(ADR-028)
|
||||
# 僅允許 http://34.143.170.20:11434、http://34.21.145.224:11434、http://192.168.0.111:11434
|
||||
# [選填] Hermes Ollama 端點;留空時自動走 GCP-A → GCP-B(111 預設不承接 Hermes 批量分析)
|
||||
# 僅允許 http://34.87.90.216:11434、http://34.21.145.224:11434、http://192.168.0.111:11434
|
||||
HERMES_URL=
|
||||
|
||||
# [預設 120] Hermes 推理 timeout(秒);批量 300 筆預估 ~90s
|
||||
HERMES_TIMEOUT=120
|
||||
# [預設 5m] Hermes runner 熱駐留;禁止回到 24h,避免 GCP-B/111 長駐高負載
|
||||
HERMES_KEEP_ALIVE=5m
|
||||
# [預設 false] 僅救急時才允許 Hermes LLM 落到 111;平時失敗交給規則/DB fallback
|
||||
HERMES_ALLOW_111_FALLBACK=false
|
||||
|
||||
# [選填] Embedding 服務主機;留空時自動走同一條 Ollama 三主機級聯
|
||||
EMBEDDING_HOST=
|
||||
# [預設 45] Embedding API timeout;優先使用 Ollama /api/embed,舊節點 fallback /api/embeddings
|
||||
EMBEDDING_TIMEOUT=45
|
||||
# [預設 30] Embedding API timeout;優先使用 Ollama /api/embed,舊節點 fallback /api/embeddings
|
||||
EMBEDDING_TIMEOUT=30
|
||||
OLLAMA_EMBED_MAX_TIMEOUT=30
|
||||
OLLAMA_EMBED_KEEP_ALIVE=1m
|
||||
PARTIAL_BACKUP_MIN_AGE_MINUTES=60
|
||||
OLLAMA_EMBED_MAX_CHARS=4000
|
||||
OLLAMA_EMBED_GCP_FAILURE_COOLDOWN_SEC=60
|
||||
OLLAMA_EMBED_GCP_FAILURE_NOTICE_SEC=30
|
||||
# [預設 true] 背景 embedding 會讀 host_health_probes,跳過最近 runtime unhealthy 的 GCP 節點
|
||||
OLLAMA_EMBED_HOST_HEALTH_SKIP_ENABLED=true
|
||||
OLLAMA_EMBED_HOST_HEALTH_SKIP_WINDOW_MINUTES=20
|
||||
|
||||
# 111 Mac final fallback guardrail and allowlist proxy
|
||||
OLLAMA_111_CIRCUIT_BREAKER_ENABLED=true
|
||||
OLLAMA_111_CIRCUIT_CACHE_SEC=60
|
||||
OLLAMA_111_CIRCUIT_WINDOW_MINUTES=60
|
||||
OLLAMA_111_CIRCUIT_PCT=5
|
||||
OLLAMA_111_CIRCUIT_MIN_TOTAL=20
|
||||
OLLAMA_111_CIRCUIT_MIN_111=5
|
||||
OLLAMA_111_USAGE_ALERT_ENABLED=true
|
||||
OLLAMA_111_USAGE_ALERT_WINDOW_MINUTES=60
|
||||
OLLAMA_111_USAGE_ALERT_PCT=5
|
||||
OLLAMA_111_USAGE_ALERT_MIN_TOTAL=20
|
||||
OLLAMA_111_USAGE_ALERT_MIN_111=3
|
||||
OLLAMA_111_USAGE_ALERT_DEDUP_SEC=3600
|
||||
OLLAMA111_PROXY_ALLOWED_CIDRS=127.0.0.1/32,192.168.0.111/32,192.168.0.188/32
|
||||
OLLAMA111_PROXY_LISTEN_HOST=192.168.0.111
|
||||
OLLAMA111_PROXY_LISTEN_PORT=11434
|
||||
OLLAMA111_PROXY_TARGET_HOST=127.0.0.1
|
||||
OLLAMA111_PROXY_TARGET_PORT=11434
|
||||
OLLAMA111_PROXY_LOG_LEVEL=INFO
|
||||
OLLAMA111_PROXY_REJECT_LOG_DEDUP_SEC=60
|
||||
|
||||
# ==========================================
|
||||
# Elephant Alpha AI Agent Super Orchestrator Settings
|
||||
@@ -174,6 +241,10 @@ ELEPHANT_ALPHA_OPENCLAW_GEMINI_ENDPOINT=https://generativelanguage.googleapis.co
|
||||
# Gemini 只能作為 Ollama 失敗備援或 ADR-028 鎖定場景,不可設為通用預設 provider
|
||||
# 取得方式:https://aistudio.google.com/app/apikey
|
||||
# 注意:Gemini 2.0 Flash 將於 2026-06-01 關閉,後續需遷移至 2.5 Flash
|
||||
# 預設硬關閉:即使 GEMINI_API_KEY 存在、fallback flag 被誤開,也不會出站產生費用
|
||||
GEMINI_API_HARD_DISABLED=true
|
||||
GEMINI_FALLBACK_ENABLED=false
|
||||
GEMINI_ALLOWED_CONTEXTS=
|
||||
GEMINI_API_KEY=<change-me>
|
||||
GEMINI_MODEL=gemini-1.5-flash
|
||||
OPENCLAW_MODEL=gemini-2.5-flash-preview-05-20
|
||||
@@ -213,8 +284,27 @@ BOT_API_TOKEN=your_bot_api_token_here
|
||||
# [必填] Post-deploy AI code review pipeline 自動修復主開關
|
||||
# ADR-020 規定預設 true(任何 finding 一律自動觸發 AiderHeal,安全網=Git+CI/CD 回滾)
|
||||
# 僅在需要短期關閉自動修復鏈時設為 false
|
||||
CODE_REVIEW_OLLAMA_MODEL=qwen3:14b
|
||||
CODE_REVIEW_OLLAMA_TIMEOUT=120
|
||||
CODE_REVIEW_OLLAMA_MODEL=qwen2.5-coder:7b
|
||||
CODE_REVIEW_OLLAMA_TIMEOUT=15
|
||||
CODE_REVIEW_OLLAMA_SECONDARY_MODEL=gemma3:4b
|
||||
CODE_REVIEW_OLLAMA_SECONDARY_TIMEOUT=60
|
||||
CODE_REVIEW_OLLAMA_FALLBACK_MODEL=hermes3:latest
|
||||
CODE_REVIEW_OLLAMA_FALLBACK_TIMEOUT=20
|
||||
CODE_REVIEW_OLLAMA_NUM_PREDICT=384
|
||||
CODE_REVIEW_OLLAMA_KEEP_ALIVE=5m
|
||||
# 預設保護 111:Code Review 這類部署後重分析只跑 GCP-A/GCP-B;需明確救急才設 true。
|
||||
CODE_REVIEW_ALLOW_111_FALLBACK=false
|
||||
CODE_REVIEW_HERMES_TIMEOUT=35
|
||||
CODE_REVIEW_HERMES_PRIMARY_MODEL=qwen2.5-coder:7b
|
||||
CODE_REVIEW_HERMES_PRIMARY_TIMEOUT=15
|
||||
CODE_REVIEW_HERMES_SECONDARY_MODEL=gemma3:4b
|
||||
CODE_REVIEW_HERMES_SECONDARY_TIMEOUT=45
|
||||
CODE_REVIEW_HERMES_FALLBACK_MODEL=hermes3:latest
|
||||
CODE_REVIEW_HERMES_FALLBACK_TIMEOUT=20
|
||||
CODE_REVIEW_HERMES_NUM_PREDICT=384
|
||||
CODE_REVIEW_HERMES_MAX_FILES=2
|
||||
CODE_REVIEW_HERMES_MAX_CHARS=900
|
||||
CODE_REVIEW_HERMES_LLM_SCAN_ENABLED=false
|
||||
CODE_REVIEW_AUTO_FIX_ENABLED=true
|
||||
|
||||
# [選填] 僅本機開發可設 true;正式環境不得允許不安全 internal webhook
|
||||
@@ -312,9 +402,14 @@ RAG_DEFAULT_TOP_K=5
|
||||
RAG_EMBED_MODEL=bge-m3:latest
|
||||
RAG_EMBED_DIM=1024
|
||||
RAG_EMBED_NORMALIZE=true
|
||||
PPT_VISION_ENABLED=false
|
||||
EMBED_CONSISTENCY_INCLUDE_111=false
|
||||
PPT_VISION_ENABLED=true
|
||||
PPT_VISION_MODEL=minicpm-v:latest
|
||||
PPT_VISION_TIMEOUT=60
|
||||
PPT_VISION_TIMEOUT=120
|
||||
PPT_VISION_MAX_SLIDES=1
|
||||
PPT_AUTO_GENERATION_ENABLED=true
|
||||
PPT_AUTO_REPORT_TYPES=all
|
||||
PPT_AUTO_DEFAULT_CATEGORY=美妝保養
|
||||
DEEPSEEK_DIRECT_ENABLED=false
|
||||
DEEPSEEK_API_KEY=
|
||||
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
|
||||
@@ -331,22 +426,52 @@ TELEGRAM_ADMIN_CHAT_ID=
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
OLLAMA_HOST=
|
||||
OLLAMA_HOST_PRIMARY=http://34.143.170.20:11434
|
||||
OLLAMA_HOST_PRIMARY=http://34.87.90.216:11434
|
||||
OLLAMA_HOST_SECONDARY=http://34.21.145.224:11434
|
||||
OLLAMA_HOST_FALLBACK=http://192.168.0.111:11434
|
||||
OLLAMA_HOST_PRIMARY_PROXY=http://192.168.0.110:11435
|
||||
OLLAMA_HOST_SECONDARY_PROXY=http://192.168.0.110:11436
|
||||
OLLAMA_RESOLVE_HOST_HEALTH_SKIP_ENABLED=true
|
||||
OLLAMA_RESOLVE_HOST_HEALTH_SKIP_WINDOW_MINUTES=20
|
||||
OLLAMA_MODEL=gemma3:4b
|
||||
OLLAMA_TIMEOUT=120
|
||||
OLLAMA_COPY_TIMEOUT=180
|
||||
OLLAMA_EMBED_TIMEOUT=45
|
||||
OLLAMA_EMBED_TIMEOUT=30
|
||||
OLLAMA_EMBED_MAX_TIMEOUT=30
|
||||
OLLAMA_EMBED_KEEP_ALIVE=1m
|
||||
OLLAMA_EMBED_MAX_CHARS=4000
|
||||
OLLAMA_EMBED_GCP_FAILURE_COOLDOWN_SEC=60
|
||||
OLLAMA_EMBED_GCP_FAILURE_NOTICE_SEC=30
|
||||
OLLAMA_EMBED_HOST_HEALTH_SKIP_ENABLED=true
|
||||
OLLAMA_EMBED_HOST_HEALTH_SKIP_WINDOW_MINUTES=20
|
||||
OLLAMA_HOST_HEALTH_MODEL_PROBE_ENABLED=true
|
||||
OLLAMA_HOST_HEALTH_MODEL_PROBE_INCLUDE_111=false
|
||||
OLLAMA_HOST_HEALTH_EMBED_MODEL=bge-m3:latest
|
||||
OLLAMA_HOST_HEALTH_EMBED_TIMEOUT=30
|
||||
OLLAMA_HOST_HEALTH_EMBED_KEEP_ALIVE=1m
|
||||
# 111 是 Mac final fallback,不承接 7B+ / vision / long-context / 長輸出任務;落到 111 時自動降級與縮短常駐。
|
||||
OLLAMA_111_MODEL_FALLBACK=llama3.2:latest
|
||||
OLLAMA_111_MODEL_DOWNGRADE_PATTERNS=qwen3:*,deepseek-r1:*,hermes3:*,llama3.1:*,qwen2.5:*,qwen2.5-coder:*,gemma3:*,minicpm-v:*,llava:*,*:7b*,*:8b*,*:14b*,*:32b*,*:70b*
|
||||
OLLAMA_111_KEEP_ALIVE=5m
|
||||
OLLAMA_111_MAX_TIMEOUT=20
|
||||
OLLAMA_111_NUM_CTX=4096
|
||||
OLLAMA_111_NUM_PREDICT=512
|
||||
|
||||
# [預設 true] OpenClaw Q&A 先走 Ollama,品質不足或失敗時才 fallback Gemini/NIM
|
||||
# 主機不提供單 caller override;一律走 OLLAMA_HOST_PRIMARY → OLLAMA_HOST_SECONDARY → OLLAMA_HOST_FALLBACK
|
||||
OPENCLAW_QA_OLLAMA_FIRST=true
|
||||
OPENCLAW_QA_OLLAMA_MODEL=qwen3:14b
|
||||
OPENCLAW_QA_OLLAMA_TIMEOUT=60
|
||||
OPENCLAW_IMAGE_VISION_MODEL=minicpm-v:latest
|
||||
OPENCLAW_IMAGE_OLLAMA_TIMEOUT=45
|
||||
OPENCLAW_IMAGE_GEMINI_MODEL=gemini-1.5-flash
|
||||
NEMOTRON_OLLAMA_FIRST=true
|
||||
NEMOTRON_OLLAMA_MODEL=qwen3:14b
|
||||
NEMOTRON_OLLAMA_TIMEOUT=180
|
||||
OPENCLAW_STRATEGY_OLLAMA_MODEL=qwen3:14b
|
||||
OPENCLAW_STRATEGY_OLLAMA_TIMEOUT=90
|
||||
OPENCLAW_STRATEGY_OLLAMA_NUM_PREDICT=2048
|
||||
OPENCLAW_STRATEGY_OLLAMA_KEEP_ALIVE=5m
|
||||
|
||||
# [預設 OFF] MCP Router;需先部署 docker-compose.mcp.yml 並完成健康檢查再開
|
||||
MCP_ROUTER_ENABLED=false
|
||||
@@ -376,23 +501,77 @@ PASSWORD_EXPIRY_DAYS=90
|
||||
# 備份 / 報表 / 同步
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
DATA_DIR=/app/data
|
||||
BACKUP_DIR=/app/data/db_backups
|
||||
BACKUP_RETENTION_DAYS=7
|
||||
DB_CONTAINER=momo-db
|
||||
REPORTS_DIR=/app/data/reports
|
||||
DATABASE_PATH=data/momo_database.db
|
||||
SQLITE_PATH=/app/data/momo_database.db
|
||||
|
||||
PG_SYNC_ENABLED=false
|
||||
PG_SYNC_INTERVAL=300
|
||||
|
||||
# PChome 競品比價與補抓產線
|
||||
COMPETITOR_INTEL_CACHE_TTL_SECONDS=21600
|
||||
PCHOME_FEEDER_TIMEOUT=12
|
||||
PCHOME_FEEDER_RATE_DELAY=1.0
|
||||
PCHOME_FEEDER_SEARCH_LIMIT=20
|
||||
PCHOME_FEEDER_MAX_SEARCH_TERMS=6
|
||||
PCHOME_FEEDER_SEARCH_MAX_PAGES=2
|
||||
PCHOME_FEEDER_SEARCH_COVERAGE_RESCUE_ENABLED=true
|
||||
# browse.sh 只作低信心/無結果的診斷計畫;正式排程預設不自動開瀏覽器。
|
||||
PCHOME_FEEDER_BROWSE_SH_DIAGNOSTIC_ENABLED=true
|
||||
PCHOME_FEEDER_BROWSE_SH_EXECUTE_ENABLED=false
|
||||
PCHOME_FEEDER_BROWSE_SH_TIMEOUT=20
|
||||
PCHOME_FEEDER_BROWSE_SH_MAX_PER_RUN=3
|
||||
PCHOME_FEEDER_BROWSE_SH_OUTPUT_PREVIEW_CHARS=1200
|
||||
PCHOME_BACKFILL_STATUS_PATH=/app/data/pchome_match_backfill_status.json
|
||||
PCHOME_BACKFILL_ACTIVE_TTL_SECONDS=7200
|
||||
|
||||
# PPT 預覽與視覺 QA
|
||||
PPT_PREVIEW_CACHE_DIR=/app/data/ppt_previews
|
||||
PPT_VISION_STATE_PATH=/app/data/ppt_vision_audit_status.json
|
||||
PPT_VISION_ACTIVE_TTL_SECONDS=7200
|
||||
PPT_VISION_IMAGE_MAX_EDGE=1280
|
||||
PPT_VISION_IMAGE_QUALITY=82
|
||||
|
||||
# Action plan hygiene / ElephantAlpha resource policy
|
||||
ACTION_PLAN_HYGIENE_STALE_HOURS=24
|
||||
ACTION_PLAN_HYGIENE_MAX_UPDATES=200
|
||||
ELEPHANT_ALPHA_RESOURCE_QUEUE_THRESHOLD=10
|
||||
ELEPHANT_ALPHA_RESOURCE_LOAD_THRESHOLD_PCT=80
|
||||
ELEPHANT_ALPHA_RESOURCE_HIGH_PRIORITY_THRESHOLD=5
|
||||
ELEPHANT_ALPHA_RESOURCE_STALE_THRESHOLD=5
|
||||
ELEPHANT_ALPHA_RESOURCE_STALE_HOURS=24
|
||||
ELEPHANT_ALPHA_RESOURCE_HYGIENE_ENABLED=true
|
||||
ELEPHANT_ALPHA_HERMES_LLM_PREFETCH_ENABLED=false
|
||||
|
||||
# [選填] 外部 BI 連結(模板全域變數)
|
||||
METABASE_URL=https://mo.wooo.work/metabase
|
||||
GRIST_URL=https://grist.wooo.work
|
||||
GRIST_URL=https://mo.wooo.work/grist
|
||||
MB_SITE_URL=https://mo.wooo.work/metabase
|
||||
GRIST_APP_HOME_URL=https://mo.wooo.work/grist
|
||||
GRIST_ADMIN_EMAIL=admin@wooo.work
|
||||
GRAFANA_PASSWORD=change-me
|
||||
PGADMIN_EMAIL=admin@wooo.work
|
||||
PGADMIN_PASSWORD=change-me
|
||||
SUPERSET_SECRET_KEY=change-me
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# 部署 / CI / smoke test 輔助變數
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
PROD_BASE_URL=https://mo.wooo.work
|
||||
SSH_JUMP_HOST=192.168.0.110
|
||||
SSH_JUMP_USER=wooo
|
||||
SSH_TARGET_HOST=192.168.0.188
|
||||
SSH_TARGET_USER=ollama
|
||||
GITHUB_OUTPUT=
|
||||
SKIP_FEEDER=false
|
||||
SKIP_HERMES=false
|
||||
SKIP_NIM=false
|
||||
SKIP_TELEGRAM=false
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# n8n Workflow Automation(monitoring profile)
|
||||
|
||||
@@ -76,6 +76,8 @@ jobs:
|
||||
run: |
|
||||
echo "short_sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
|
||||
echo "message=$(git log -1 --pretty=%s | head -c 60)" >> $GITHUB_OUTPUT
|
||||
VERSION=$(sed -n "s/^SYSTEM_VERSION[[:space:]]*=[[:space:]]*[\"']\([^\"']*\)[\"'].*/\1/p" config.py | head -1)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT
|
||||
|
||||
# 偵測是否需重建 Docker image(force_rebuild 優先,其次看變更檔案)
|
||||
@@ -87,6 +89,14 @@ jobs:
|
||||
echo "label=🔨 強制重建 Docker Image" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")
|
||||
if echo "$CHANGED" | grep -qE '^(Dockerfile|requirements\.txt|docker-compose\.yml)$'; then
|
||||
echo "type=rebuild" >> $GITHUB_OUTPUT
|
||||
echo "label=🔨 重建 Docker Image" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "type=sync" >> $GITHUB_OUTPUT
|
||||
echo "label=📁 同步 runtime 檔案" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: 偵測 AI 觀測台前端 QA 是否需要執行
|
||||
id: observability_qa
|
||||
@@ -99,14 +109,6 @@ jobs:
|
||||
else
|
||||
echo "ℹ️ AI 觀測台 QA: skipped"
|
||||
fi
|
||||
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")
|
||||
if echo "$CHANGED" | grep -qE '^(Dockerfile|requirements\.txt|docker-compose\.yml)$'; then
|
||||
echo "type=rebuild" >> $GITHUB_OUTPUT
|
||||
echo "label=🔨 重建 Docker Image" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "type=sync" >> $GITHUB_OUTPUT
|
||||
echo "label=📁 同步 Python 檔案" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# 設定 SSH 金鑰 + 主機驗證(C2 fix: 移除 StrictHostKeyChecking no)
|
||||
- name: 設定 SSH 金鑰
|
||||
@@ -313,6 +315,7 @@ jobs:
|
||||
- name: 健康檢查
|
||||
env:
|
||||
COMMIT_SHA: ${{ steps.commit.outputs.short_sha }}
|
||||
EXPECTED_VERSION: ${{ steps.commit.outputs.version }}
|
||||
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
run: |
|
||||
@@ -332,6 +335,14 @@ jobs:
|
||||
[ "$i" -eq 12 ] && echo "❌ HTTP 健康檢查失敗" && exit 1
|
||||
sleep 15
|
||||
done
|
||||
EXTERNAL_HEALTH=$(curl -fsS --max-time 10 https://mo.wooo.work/health)
|
||||
EXTERNAL_VERSION=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('version',''))" <<< "$EXTERNAL_HEALTH")
|
||||
if [ "$EXTERNAL_VERSION" != "$EXPECTED_VERSION" ]; then
|
||||
echo "❌ 正式版本未更新:expected=$EXPECTED_VERSION actual=$EXTERNAL_VERSION"
|
||||
echo "$EXTERNAL_HEALTH"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ 正式版本驗證通過:$EXTERNAL_VERSION"
|
||||
# 驗證三應用容器均在 Running 狀態
|
||||
ssh -i ~/.ssh/id_deploy ollama@192.168.0.188 \
|
||||
'RUNNING=$(docker ps --format "{{.Names}}" | grep -cE "momo-(pro-system|scheduler|telegram-bot)" || true); \
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -66,6 +66,7 @@ data/*.db-wal
|
||||
data/*.sqlite
|
||||
data/*.sqlite3
|
||||
data/*.lock
|
||||
data/ppt_vision_audit_status.json
|
||||
data/*.pkl
|
||||
database/*.db
|
||||
database/*.db-journal
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
server {
|
||||
listen 11435;
|
||||
location / {
|
||||
proxy_pass http://34.143.170.20:11434;
|
||||
proxy_pass http://34.87.90.216:11434;
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
|---|---|---|
|
||||
| 110 | `192.168.0.110` | Gateway、Nginx、Gitea、n8n、Superset |
|
||||
| 188 | `192.168.0.188` | App、DB、生產容器、AutoHeal target(不可作為 Ollama 節點) |
|
||||
| GCP-SSD-1 | `34.143.170.20` | Primary Ollama (High Performance SSD, All Models) |
|
||||
| GCP-SSD-1 | `34.87.90.216` | Primary Ollama (High Performance SSD, All Models) |
|
||||
| GCP-SSD-2 | `34.21.145.224` | Secondary Ollama (SSD Optimized, Redundancy) |
|
||||
|
||||
## 6. 核心服務
|
||||
@@ -129,7 +129,7 @@
|
||||
- `gunicorn.conf.py` 必須透過 `docker-compose.yml` bind mount 進 `momo-app`;除救急外,不以 `docker cp` 當常態部署方式。
|
||||
- CD rebuild 應先完成 image build,再短暫 recreate 三應用容器;禁止把 no-cache build 時間變成長時間 502。
|
||||
- HTTP health / Blackbox / CD 探測必須打 `/health`,不可打 Dashboard 首頁 `/`,避免監控流量觸發重型查詢造成 worker starvation。
|
||||
- 所有 AI Agent / LLM / embedding 呼叫必須 Ollama-first,且只允許 GCP-A `34.143.170.20:11434` → GCP-B `34.21.145.224:11434` → 111 `192.168.0.111:11434` 三主機級聯;Gemini 只能作為備援或 ADR-028 鎖定場景,188 不可作為 Ollama 節點。
|
||||
- 所有 AI Agent / LLM / embedding 呼叫必須 Ollama-first,且只允許 GCP-A `34.87.90.216:11434` → GCP-B `34.21.145.224:11434` → 111 `192.168.0.111:11434` 三主機級聯;Gemini 只能作為備援或 ADR-028 鎖定場景,且預設由 `GEMINI_API_HARD_DISABLED=true` 硬封鎖,188 不可作為 Ollama 節點。
|
||||
|
||||
## 8. 常用入口
|
||||
|
||||
@@ -139,6 +139,9 @@
|
||||
- 前端更版路線圖: `docs/guides/frontend_upgrade_roadmap.md`
|
||||
- AI 觀測台 UI 治理: `docs/guides/observability_ui_governance.md`
|
||||
- AI 自動化 Session SOP: `docs/guides/ai_automation_session_sop.md`
|
||||
- Browse.sh 爬蟲診斷手冊: `docs/guides/browse_sh_crawler_playbook.md`
|
||||
- Webcrumbs 共用 UI Runtime: `docs/guides/webcrumbs_shared_runtime.md`
|
||||
- 外部專業 Benchmark: `docs/guides/external_professional_benchmark.md`
|
||||
- AI 競價情報 SOT: `docs/AI_INTELLIGENCE_MODULE_SOT.md`
|
||||
- Agent 角色矩陣: `docs/guides/codex_agent_roles.md`
|
||||
- ADR 索引: `docs/adr/README.md`
|
||||
|
||||
@@ -140,6 +140,12 @@
|
||||
- ❌ **禁止**: 使用 mock data、假商品、假 KPI、假排程、假使用者、假頁面或純展示用 placeholder 冒充已完成。
|
||||
- ❌ **禁止**: 為了符合原型畫面而改寫或捏造業務數字。
|
||||
|
||||
### 第 14.2 條:前端文案與工作溝通隔離(絕對禁止違反)
|
||||
- ✅ **正確**: 前端頁面只放使用者完成任務所需的產品文案、狀態、操作入口與可診斷錯誤。
|
||||
- ✅ **正確**: 施工紀錄、版本發布說明、AI 工作視窗判斷、Session 溝通、TODO 內容,只能放在文件、日誌或提交訊息,不得搬到使用者可見頁面。
|
||||
- ❌ **禁止**: 在模板、靜態 JS/CSS 可見文案中放入「本輪已完成」「剛剛修正」「Codex/Claude 評估」「V10.x hotfix」「推到 Gitea」等工作視窗溝通內容。
|
||||
- ❌ **禁止**: 用內部工程語氣代替產品語氣,例如把頁面寫成施工報告、交接紀錄或 agent 工作摘要。
|
||||
|
||||
---
|
||||
|
||||
## 第五章:系統架構規範
|
||||
@@ -170,11 +176,13 @@
|
||||
- ❌ **禁止**: 用會觸發大量 DB 查詢或模板渲染的頁面作為探測目標,避免監控流量本身造成 worker starvation。
|
||||
|
||||
### 第 18.2 條:AI / LLM 路由主機紅線(絕對禁止違反)
|
||||
- ✅ **正確**: 所有 AI Agent、LLM 推理與 embedding 預設必須走 Ollama 三主機級聯:GCP-A `34.143.170.20:11434` → GCP-B `34.21.145.224:11434` → 111 `192.168.0.111:11434`。
|
||||
- ✅ **正確**: 所有 AI Agent、LLM 推理與 embedding 預設必須走 Ollama 三主機級聯:GCP-A `34.87.90.216:11434` → GCP-B `34.21.145.224:11434` → 111 `192.168.0.111:11434`。
|
||||
- ✅ **正確**: 所有通用文字生成、Q&A 第一響應、Hermes、NemoTron qwen3 路徑、AiderHeal 與 embedding 必須透過 `services/ollama_service.resolve_ollama_host()` 或同等核准 wrapper 取得主機。
|
||||
- ✅ **正確**: Gemini 只能作為 Ollama 主路徑失敗後的備援,或 ADR-028 明確鎖定的低頻特殊場景。
|
||||
- ✅ **正確**: `GEMINI_API_HARD_DISABLED` 預設必須為 `true`,`GEMINI_FALLBACK_ENABLED` 預設必須為 `false`;即使 `GEMINI_API_KEY` 存在,也不得出站呼叫 Gemini,除非操作員明確解除 hard switch 並開啟緊急備援。
|
||||
- ❌ **禁止**: 將 `AI_PROVIDER`、`OLLAMA_HOST`、`HERMES_URL`、`EMBEDDING_HOST`、`OLLAMA_API_BASE` 指向非 GCP-A / GCP-B / 111 的 Ollama 端點。
|
||||
- ❌ **禁止**: 新增 Gemini-first 的 AI Agent、LLM caller 或把 Gemini 設為通用預設 provider;新增 Gemini caller 必須走 ADR review。
|
||||
- ❌ **禁止**: 繞過 `services.gemini_guard` 直接初始化 Gemini SDK 或直接用 `GEMINI_API_KEY` 打 Google Gemini REST API。
|
||||
- ❌ **禁止**: 使用 188 主機作為 Ollama 節點;188 只作為 App/DB/容器宿主與 AutoHeal target。
|
||||
|
||||
---
|
||||
@@ -182,7 +190,7 @@
|
||||
## 第六章:版本管理規範
|
||||
|
||||
### 第 19 條:版本號更新(強制要求)
|
||||
- ✅ **正確**: 每次功能更新必須修改 `app.py` 的 `SYSTEM_VERSION`
|
||||
- ✅ **正確**: 每次功能更新必須修改 `config.py` 的 `SYSTEM_VERSION`;`app.py` 僅從 config 匯入顯示。
|
||||
- ✅ **格式**: `V主版本.次版本` (例如: V9.4)
|
||||
- ❌ **禁止**: 修改功能但不更新版本號
|
||||
|
||||
@@ -458,7 +466,7 @@
|
||||
|---------|------|------------|
|
||||
| `config.py` | 系統配置 | `DATABASE_PATH`, `PUBLIC_URL` |
|
||||
| `database/models.py` | 資料模型 | `Product.i_code` 定義 |
|
||||
| `app.py` | 主程式 | `SYSTEM_VERSION`, `TAIPEI_TZ` |
|
||||
| `app.py` | 主程式 | `TAIPEI_TZ` |
|
||||
| `dashboard.html` | 商品看板 | 主題色系、響應式設計 |
|
||||
| `daily_sales.html` | 業績看板 | 行事曆邏輯、圖表配置 |
|
||||
| `scheduler.py` | 排程爬蟲 | 商品圖 CDN URL 構造 |
|
||||
|
||||
@@ -10,6 +10,7 @@ RUN apt-get update && apt-get install -y \
|
||||
g++ \
|
||||
curl \
|
||||
libpq-dev \
|
||||
postgresql-client \
|
||||
# Chrome/Selenium 依賴
|
||||
wget \
|
||||
gnupg \
|
||||
@@ -32,6 +33,7 @@ RUN apt-get update && apt-get install -y \
|
||||
fonts-liberation \
|
||||
fonts-noto-cjk \
|
||||
fonts-noto-cjk-extra \
|
||||
libreoffice-impress \
|
||||
openssh-client \
|
||||
libappindicator3-1 || true \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
The system contains hardcoded database passwords in Kubernetes configuration files, which poses a security risk.
|
||||
|
||||
## Current Issues
|
||||
1. **Hardcoded passwords**: `k8s/01-secrets.yaml` and `k8s/gcp/01-secrets.yaml` contain hardcoded password `"wooo_pg_2026"`
|
||||
1. **Hardcoded passwords**: `k8s/01-secrets.yaml` and `k8s/gcp/01-secrets.yaml` contain hardcoded password `"<POSTGRES_PASSWORD>"`
|
||||
2. **Missing environment configuration**: `.env.example` was missing database password configuration (now fixed)
|
||||
|
||||
## Security Recommendations
|
||||
@@ -45,7 +45,7 @@ Replace hardcoded values in:
|
||||
**Before (INSECURE):**
|
||||
```yaml
|
||||
stringData:
|
||||
POSTGRES_PASSWORD: "wooo_pg_2026"
|
||||
POSTGRES_PASSWORD: "<POSTGRES_PASSWORD>"
|
||||
```
|
||||
|
||||
**After (SECURE):**
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
```bash
|
||||
# 1. 立即更換所有已外洩的憑證
|
||||
# 當前已外洩的憑證包括:
|
||||
# - LOGIN_PASSWORD: 0936223270
|
||||
# - TELEGRAM_BOT_TOKEN: 8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg
|
||||
# - LOGIN_PASSWORD: <LOGIN_PASSWORD>
|
||||
# - TELEGRAM_BOT_TOKEN: <TELEGRAM_BOT_TOKEN>
|
||||
# - LINE_CHANNEL_ACCESS_TOKEN
|
||||
# - EMAIL_HOST_PASSWORD: jopokbhdpnnborjd
|
||||
# - NGROK_AUTH_TOKEN: 36e27NM5V7sUJ8QxJIAAWCp7sUv_3brtcrBarYvcP3SbvFKhF
|
||||
|
||||
@@ -4,6 +4,244 @@
|
||||
================================================================================
|
||||
|
||||
【已完成】
|
||||
- V10.601 收斂 Gemini / 111 治理與全 repo 已知密鑰清除:正式 `ai_calls` 近 24 小時與近 7 天 provider 彙總未見 Gemini 出站;舊 K8s manifest、n8n workflow、監控/auto-repair scripts、Superset 文件、Google Drive token 檔與歷史文件中的已知實密鑰全部改為占位符,並補測試禁止 Google API/OAuth key、Telegram token、Ollama Cloud key、Superset 預設密碼再次入庫;OpenClaw 日/週/月/Meta 等敘事長報告改為 GCP-A/GCP-B only,不再讓 `openclaw_meta` 在 GCP 超時後落到 111,避免 111 被長文生成壓高負載。
|
||||
- V10.600 收斂 AI Intelligence 競品表前台文案:PChome 競品卡片 footer 不再顯示 `TTL: 6h`、比對門檻等工程參數,改為「僅顯示已通過身份比對的競品」;`identity_v2`、`match_type_exact`、`price_alert_exact`、`evidence_*`、`match_*` 等內部診斷 tag 只會轉成營運可讀的中文 badge,未知 tag 直接隱藏,避免把 matcher 內部碼或實驗性標記露給使用者。
|
||||
- V10.599 重整 PChome 比價覆核工作台 UX 並補全站巡檢能力:覆核頁不再沿用首頁商品表格,也不再把 `matcher_rescore`、`stored_status`、`rescore_accepted_current`、`HITL`、`COMPLETE` 等內部診斷/狀態碼輸出到前台或 tooltip;改為「商品 / MOMO、PChome 候選、覆核判讀、下一步、紀錄」六欄工作流。同步修正 catalog review status 的前台語義、決策信封中文標籤、局部 1540px 橫向工作台、手機版欄位 label,並把覆核狀態分段列改為自適應 grid,避免 chip 造成桌面/平板/手機視覺溢出;`check_responsive_overflow.js` 改為逐頁輸出、HTTPS context、commit+body ready、timeout 後安全收尾,讓桌面/平板/手機全站 UX 巡檢可追蹤;topbar AI 觀測台 indicator 增加前端 60 秒 session cache / 2.5 秒 abort 與後端 30 秒 cache,避免每頁跳轉重複打 DB 查詢拖慢全站;`market_intel/disabled.html` 從 1MB 大型停用頁改為輕量狀態頁,保留狀態與正式操作入口,避免停用模組拖慢巡檢與使用者操作;新增憲法第 14.2 條與測試 guard,禁止把工作視窗溝通、施工紀錄或版本發布說明放到使用者可見前端頁面;ICAIM 競情 API 改為 120 秒短快取、5 秒 PostgreSQL statement timeout、stale 快照降級與 LATERAL 最新價查詢,避免 AI 競情看板重查詢拖慢全站。
|
||||
- V10.584 補 PChome Nick 去重與 stale recovery 單品窄門:`Nick` 先去 HTML / 行銷星號 / 重複品名,避免 `29g`、`100ml` 被同一商品副標重複計數成 `component_count_conflict`;同步新增 NIVEA 妮維雅霜 100ml、Schick 舒綺敏感肌除毛刀片 3 入、TS6 沁涼潔淨慕斯 100g 的具名 exact total-price alignment。IBL 沐浴精+洗髮精 vs 洗髮精仍保留 identity review,唇釉色號/目錄款與 Paula's Choice 效期/金蓋差異仍不自動寫正式價差。
|
||||
- V10.583 補 Paula's Choice 身體乳 PChome Nick 具名 alignment:`2%水楊酸身體乳210ml二入` 可和 PChome `Nick` 補出的 `水楊酸身體乳雙入組 / 210ml x2` 對齊,進 `exact / total_price / price_alert_exact`;但 `118ml二入組(金蓋限定版)` 對上 PChome 效期品仍保留 `manual_review / identity_review`,不泛用放寬中文入數。
|
||||
- V10.582 補 PChome 比價通知專業分級與 Nick 副標身份證據:NemoTron 價格決策信封現在保留 `momo_price`、`competitor_price`、`candidate_gap_pct` 與 `sales_7d_delta_pct`,EventRouter / Telegram 模板會把 `match_type / price_basis / alert_tier` 翻成「直接價格威脅、單位價覆核、身份覆核、壓制告警」與操作邊界;PChome crawler 會保留 `Nick` 副標為 `match_name` 給 matcher 使用,UI/DB 顯示仍維持原品名,讓容量、入數、濃度資訊可參與比對。
|
||||
- V10.581 將 V10.580 的重複單品組安全線接進 PChome retryable revalidation 窄門:只允許 `true_low_confidence`、舊診斷為 `match_type=exact / price_basis=manual_review`、無阻擋原因且命中具名安全商品線(Bioneo 150ml x2、Cetaphil 150ml x2、Avene 300ml x4、Schick 2+1 入、KOSE 雪肌精 500ml x2 禮盒)的候選進小批次重評;仍由最新版 matcher 最終判斷是否寫入正式 `competitor_prices`。
|
||||
- V10.580 補 PChome 重複單品組 total-price 窄門:同品牌、同入數、同基礎規格且名稱高度對齊的 150ml x2、300ml x4、2+1 入等候選,可由 `exact / manual_review` 進 `exact / total_price / price_alert_exact`,正式部署前估算 213 筆高分 `true_low_confidence` 中只有 7 筆會轉自動寫入。同步新增 NEW DIRECTIONS 甜杏仁油 vs 杏桃核仁油核心油種 hard veto,避免規格一樣但油種不同的錯配污染正式價差;Paula's Choice 這類 PChome 端缺 30ml 規格的雙入組仍保留 manual review,不放寬全域門檻。
|
||||
- V10.579 補 PChome 高信心 total-price safe family:SAB 私密防護舒緩噴霧 30ml 與 Herbacin 小甘菊 20ml 護手霜在同款式、同規格、無 variant/commercial gap 時可由 focused matcher 進 `exact / total_price / price_alert_exact`,讓近門檻重評能真正寫入正式比價;Herbacin 柔皙 vs 野生玫瑰等跨 variant 仍保留在 review,不放寬全域門檻。同版將 Code Review GCP-B secondary timeout 預設由 60 秒收斂到 25 秒,GCP-A preflight 不通且 GCP-B 生成卡住時更快回 deterministic local degraded,不呼叫 Gemini/111。
|
||||
- V10.578 修正 Code Review 靜態掃描 timeout 誤報:Hermes deterministic scan 對 `requests.get/post/...` 會檢查同一呼叫 block 的後續行,多行呼叫已帶 `timeout=` 時不再報「HTTP request 未設定 timeout」。避免 V10.577 的 preflight helper 因多行格式被自己誤判為 MEDIUM。
|
||||
- V10.577 補 Code Review Ollama host preflight:OpenClaw 架構評估在 explicit GCP host generate 前先以短 `/api/version` 探測健康度,GCP-A 不通時會快速跳 GCP-B,不再等 15 秒 generate timeout;仍維持 GCP-A/GCP-B 優先、111 預設禁用、Gemini hard-disabled 預設不呼叫。
|
||||
- V10.576 補 PChome backfill backlog 的型錄 lane counts,讓 `/api/ai/pchome-match/backfill/status` 也能回傳 `catalog_variant_review`、`catalog_unit_review`、`catalog_identity_review` 三條操作隊列;同版修正 `OllamaService.generate(allow_111_fallback=False)`,當 lazy resolver 快取到 111 時會強制改試 GCP-A/GCP-B allowlist,不再直接 `all 0 hosts failed`,且仍不把長分析推給 111。
|
||||
- V10.575 拆分 PChome 型錄可比覆核 lane:`catalog_comparable` 不再只是一個總數,正式拆成 `catalog_variant_review`(選項/色號/款式待核)、`catalog_unit_review`(入數/檔期/商業條件待核)與 `catalog_identity_review`(身份採用待核)。Coverage、review queue filter、Dashboard 分段、decision envelope、Webcrumbs host data 都共用同一套 SQL helper 與 metadata;仍維持 HITL、不自動寫正式價差,讓營運可批次清理最有機會轉成單位價或正式身份的候選。
|
||||
- V10.574 接上 PChome 型錄/任選可比覆核隊列:沿用 V10.572 的 `catalog_comparable_count` 安全口徑,將高分、無 hard veto、具同品線身份證據但仍有任選/型錄/商業條件待確認的 `true_low_confidence` 候選,拆成獨立 `catalog_comparable` 篩選與 decision envelope。此隊列仍維持 HITL,不寫入正式 `competitor_prices`、不算 exact matched,並把「型錄可比」與真正「證據不足」分開,讓營運可以先批次處理最有機會轉成單位價或正式身份的候選。
|
||||
- V10.574 新增市場情報 Source Governance → Fetch Target bridge:新增 `/api/market_intel/mcp_fetch_target_source_governance_review`、市場情報頁 bridge panel 與 deployment readiness smoke target,交叉審核 Professional Source Governance 與 MCP Fetch Target Review,要求每個 target `platform_code/source_key` 都能對上已通過治理的公開 source contract;仍不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不執行 CLI、不掛 scheduler。
|
||||
- V10.572 新增 PChome 決策支援覆蓋率:不放寬 `matched` / `decision_ready` 的 exact identity 門檻,另外把高分、無 hard veto、具同品線與規格證據,但因「任選 / 色號 / 型錄 / 即期」仍需覆核的候選,納入 `catalog_comparable_count` 與 `decision_support_rate`。Dashboard、當日業績、成長分析與 backfill 狀態摘要同步顯示「決策支援覆蓋率 / 精準可告警覆蓋 / 型錄可比 / 單位價」,讓覆蓋率提升建立在可解釋情報分層上,而不是把非 exact 商品硬寫成正式同款。
|
||||
- V10.571 提升 PChome pending 覆蓋率搜尋召回:`PCHOME_FEEDER_MAX_SEARCH_TERMS` 預設由 5 提升到 6,新增 `PCHOME_FEEDER_SEARCH_COVERAGE_RESCUE_ENABLED`,在主要搜尋詞與原始名稱 fallback 之間插入狹義 coverage rescue terms。搜尋詞會保留 `5.5g`、`2.4g` 等小數規格,不再變成 `5 5g` / `2 4g`;同時排除外出清潔、卸除髒汙、卸防曬等非身份核心噪音。正式 pilot 顯示 CeraVe / TUNEMAKERS / Embryolisse / Neogence / NIVEA 這類雙語品牌商品常卡在 PChome 搜尋召回,因此補上「英文品牌 + 中文品牌 + 核心身份 + 規格」窄搜尋詞;「品牌 + 品類 + 規格」仍只開給安全品類,避免為了拉 pending 覆蓋率引入假陽性。
|
||||
- V10.570 補 PChome 身份 / 報價證據契約:matcher 的 `match_diagnostic_json` 新增 `identity_evidence`、`offer_evidence`,把品牌、品類、identity anchor、型號、規格、入數與 variant guardrail 拆成結構化證據;覆核隊列與 decision envelope 新增 `difference_highlights`,可直接指出容量、入數、色號、香味、款式、補充包、檔期組合等差異。價格明確標記為 offer evidence,不再被誤當身份證據,Dashboard / PPT / OpenClaw / Webcrumbs 能共用同一份比對證據。
|
||||
- 外部專業 benchmark 固定節奏:已建立每週一 09:30 自動檢視,並新增 `docs/guides/external_professional_benchmark.md`,把 Google Merchant Center、Google Product structured data、Schema.org Product/Offer/AggregateOffer 與 Baymard 電商 UX 做法轉成可落地準則:identity evidence、fresh offer、review 差異高亮、PPT/AI evidence 分層。
|
||||
- V10.565 補 PChome 覆蓋率操作建議:`/api/ai/pchome-match/backfill/status` 會把低覆蓋率拆成 `operation_backlog`,分別列出刷新舊 identity、重評近門檻、補抓未配對、人工覆核、單位價覆核與過期搜尋救援預覽;同時回傳 `recommended_next_action`,Dashboard 狀態摘要會顯示「建議執行比價補強 / 刷新過期 identity / 處理覆核」等下一步,讓覆蓋率 KPI 直接連到可執行行動。
|
||||
- V10.563 收斂正式 preview 假可救候選:M.A.C 超持妝輕透濾鏡蜜粉若只有 PChome 端出現明確色號(例如 `#絕絕紫`),會標成 `variant_selection_review` 並維持 `true_low_confidence`,不再佔 recoverable 池;SAUGELLA 賽吉兒菁萃潔浴凝露新增潤澤 / 日用型 / 加強 / 黃金女郎型變體互斥,避免同品線不同私密清潔款式被誤救成 matched。
|
||||
- V10.566 新增市場情報 Professional Source Governance:把 robots/REP、sitemap/lastmod、JSON-LD / schema.org structured data、canonical URL、rate limit、公開資料邊界、provenance、snapshot hash 與 idempotency key 變成可審核 source contract。新增 `/api/market_intel/mcp_professional_source_governance` 與市場情報頁卡片、deployment readiness smoke target;API/UI 只審核操作員貼回的治理摘要,不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不掛 scheduler,後續 fetch target review 才能引用通過治理的來源。
|
||||
- V10.561 補 PChome 比價補強前端分段回饋:Dashboard 的 PChome 卡片從「補抓產線」改為「比價補強產線」,按鈕與確認文案同步說明會先刷新舊 identity、再重評近門檻與補抓未配對;結果區新增刷新 / 重評 / 補抓三段 matched/total 摘要,避免後端已完成分段統計但操作員仍只看到一個籠統成功數。
|
||||
- V10.560 串起手動 PChome 比價補強三段式流程:`/api/ai/pchome-match/backfill` 現在不只跑近門檻重評與未配對補抓,也會先用小批次 `run_expired_identity_refresh()` 刷新已知 `identity_v2` 舊價格,讓操作員按一次補強就能同時處理「舊 identity 新鮮度」、「near-threshold low_score」與「pending identity」三條主線。結果 payload 新增 `stale_identity_refresh` 分段統計,方便後續 Dashboard / 簡報 / AI 決策知道覆蓋率改善是來自刷新、重評或補抓。
|
||||
- V10.559 收斂 retryable 有效身份新鮮度:`_fetch_retryable_candidate_skus()` 不再把 `expires_at IS NULL` 的舊 PChome `identity_v2` 當成有效阻擋條件,只有明確 `expires_at > CURRENT_TIMESTAMP` 的新鮮 identity 才會阻止 near-threshold revalidation。未知新鮮度仍走 V10.551 的 expired / recovery 刷新入口,重評後仍必須通過最新版 matcher、hard-veto、auto write safety 與既有正式候選覆寫保護,避免為了拉覆蓋率犧牲準確率。
|
||||
- V10.558 補 legacy focused identity reason 回刷窄門:舊 attempt 若沒有新版 `focused_exact_total_price_safe` marker,但已帶具名 `focused_exact_identity_*` 且該 identity 屬於 matcher total-price safe set,並且舊分數已達全域 `MIN_MATCH_SCORE`,可進近門檻重評。這補上歷史資料缺 marker 的漏接情境;仍要求無 hard veto、`exact_identity`、無 commercial / variant / count / bundle 阻擋,最後由最新版 matcher 決定是否能寫正式價差。
|
||||
- V10.557 收緊 focused reason-based 回刷 guard:上一版 reason-based 回刷現在不只要求 `focused_exact_total_price_safe`,還必須同時命中一條具名 `focused_exact_identity_*` 且該 identity 屬於 matcher 的 total-price safe set。這避免未來只有總開關、缺少具名身份證據的舊 attempt 被納入回刷;rom&nd / Solone / Summer’s Eve 等 review-only focused line 仍被測試鎖在自動價差線外。
|
||||
- V10.556 修 Ollama GCP-B model fallback:GCP-B 若缺 coder/large 指定模型,先用 host-compatible fallback `gemma3:4b` 留在 GCP-B,不直接把流量推到 111;`model not found` 404 視為模型缺失,不再把整台 GCP-B 標 unhealthy。主機順序仍維持 GCP-A → GCP-B → 111。
|
||||
- V10.555 補 focused total-price reason-based 回刷:`_fetch_retryable_candidate_skus()` 新增一條結構化 reason 窄門,只要舊 attempt 已帶 `focused_exact_total_price_safe` 且命中 matcher 的 `FOCUSED_IDENTITY_TOTAL_PRICE_REASONS`,即可進近門檻重評;仍要求無 hard veto、`exact_identity`、分數下限,並排除 commercial / variant / count / bundle 等阻擋理由。這讓已經被 matcher 明確判為 total-price exact 的舊候選不再依賴手寫商品名 SQL 才能回刷,同時 rom&nd / Solone / Summer’s Eve 等 review-only 品線仍不會進自動價差線。
|
||||
- V10.554 接線香氛 / 精油 focused exact 回刷:Herb24 晨霧純精油擴香儀黑色、Pavaruni 40 香味 10ml 精油、Pavaruni 20 香味 450g 香氛蠟燭、Derma 大地有機植萃護膚油 150ml 現在明確標記 `focused_exact_total_price_safe`,並接進 `_fetch_retryable_candidate_skus()` 近門檻舊候選回刷。此入口只收 `low_score / refresh_low_score / true_low_confidence` 中命中精準名稱錨點、無 hard veto、`exact_identity` 且沒有變體 / 商業條件 / 件數衝突的候選;Laundrin、好物良品融蠟燈、Yuskin 等仍保留人工覆核,不為了拉覆蓋率強推自動價差。
|
||||
- V10.553 優化 current PPT/AI 比價結果查詢:`fetch_competitor_comparison_results()` 的 current/latest MOMO 價格改用 `JOIN LATERAL` 取單品最新價,移除 `ROW_NUMBER() OVER (PARTITION BY p.id ...)` window scan;歷史報表的 `end_date` cutoff 仍保留在 lateral 子查詢內,維持「指定期間截止日前最新 MOMO 價」語意不變。這能降低簡報、OpenClaw/AI payload 與比價匯出在大量 price_records 下的查詢成本。
|
||||
- V10.552 收斂決策查詢的新鮮度口徑:`fetch_top_competitor_risks()`、PChome review queue、review sample 與 current PPT/AI 比價結果都不再把 `expires_at IS NULL` 當成有效現價,只接受 `expires_at > CURRENT_TIMESTAMP` 的 PChome identity_v2 價格。未知新鮮度只留在 coverage 的診斷欄位與 V10.551 刷新入口,不再進入價格風險、簡報、AI 決策或覆核排除條件。
|
||||
- V10.551 收斂未知新鮮度刷新與補抓排序:`_fetch_expired_identity_skus()` / `_fetch_expired_identity_recovery_skus()` 將 `expires_at IS NULL` 視為必須刷新或可搜尋救援的未知新鮮度 identity,和 V10.549「未知新鮮度不算可決策覆蓋率」口徑對齊;兩條路徑改用 `JOIN LATERAL` 取最新 MOMO 價,移除 product-wide window scan。`_fetch_unmatched_priority_skus()` 也改用 lateral 最新價,並優先重搜低風險 `no_result / refresh_no_result`,讓 V10.550 的安全召回詞先用在最可能被救回的商品。
|
||||
- V10.550 補安全搜尋召回詞:`_build_variant_recall_search_plan()` 對低風險穩定品類新增 `品牌 + 品類` 的補搜尋詞,讓 `no_result / refresh_no_result` 更有機會找到 PChome 候選後再交給 matcher 安全判斷;美甲片、指甲油、唇彩、香氛/精油、粉底、防曬、任選/色號/款式等高 variant 風險商品不走通用召回,DASHING DIVA 仍只走既有 line-specific recall + sort fallback。此變更不改 `MIN_MATCH_SCORE`、hard veto、fresh-search write safety 或 stronger existing match 覆寫保護。
|
||||
- V10.549 收斂比價新鮮度 KPI 口徑:coverage cache 升到 v10,`expires_at IS NULL` 不再算進「可用比價 / decision ready」,改拆成 `unknown_freshness_matches` / `unknown_freshness_count`,避免沒有到期時間的舊資料被當成可直接決策的新鮮價格。Dashboard / daily / growth 同步顯示未知新鮮度與「未形成有效身份配對」,並把 PChome/MOMO 價格方向文案改成 `PChome 價格壓力` / `MOMO 價格優勢`,降低誤讀。
|
||||
- V10.548 接線更多 focused exact 舊候選回刷:把 matcher 已驗證可安全走 total-price 的 3W CLINIC 膠原蛋白粉底液 50ml x2、花美水 Moisture/Inclear 1.7g x3、KUSSEN 寶寶益菌屁屁膏 50ml 3 入、Lab52 齒妍堂嬰幼兒/汪汪隊牙刷 2 入接進 `_fetch_retryable_candidate_skus()` focused true-low / rescore 窄門。這只擴大「舊候選可被新版 matcher 重評」的入口,不改 `MIN_MATCH_SCORE`、hard veto、auto price write safety 或既有覆寫保護。
|
||||
- V10.547 強化單位價覆核洞察:`manual_unit_price_required` 不再只是人工狀態,覆核隊列與商品看板會重新帶出單位價換算、MOMO/PChome 單位價方向、差距百分比與處理建議;決策信封 / OpenClaw / PPT 摘要可讀到 `unit_price_insight`。人工覆核寫回也會保留原始 `match_diagnostic_json` / comparison mode / diagnostic codes,避免後續簡報、審計或 AI 策略只剩人工文案而失去 matcher 證據鏈。
|
||||
- V10.546 補近門檻舊候選回刷隊列:`run_retryable_candidate_revalidation()` 新增 `legacy_unmasked_attempt`,當最新狀態是 `no_result` / `refresh_no_result` / `expired_match` 時,可回撈同 SKU 早期近門檻候選交給最新版 matcher 重評;仍要求 candidate id、分數下限、無 hard veto、exact_identity,且不打開人工否決、單位價、identity_veto 或 protected existing match。
|
||||
- V10.545 收斂 Dashboard 比價覆蓋率口徑:coverage cache 升到 v9,新增身份覆蓋、可用比價、新鮮度、待補身份、過期身份與人工閉環欄位;商品看板和 PChome 覆核頁改只把真正待處理狀態算進「比價覆核」,人工已否決 / 人工單位價 / 需補研究改列為人工閉環;PChome competitor map 只吃有效價格、新鮮、identity_v2 最新 row,資料新鮮度也改看可用比價 row。
|
||||
- V10.544 收斂變體安全與 YES 指甲工具線:新增 YES 德悅氏指甲剪附除垢銼刀、腳皮銼腳板、藍寶石銼刀、三面拋光棒與 6/8cm 指甲剪的精準 total-price 線,要求同品牌、同工具名稱、同尺寸與同亮面/霧面/可收納/三面/不掉屑等款式訊號;同步接進 revalidation SQL。新增 MUJI / COCODOR 未知香味差異與 OPI 無型號不同色名 hard veto,HOOOME 暖燈材質差留人工覆核,搜尋詞也會優先帶香味/色名,提升 crawler 精準候選率。
|
||||
- V10.543 打通 `rescore_accepted_current` 窄門回刷:已進人工覆核池的候選若命中具名 focused exact 線,可進 `run_retryable_candidate_revalidation()` 重新評分;新增 SK-II 青春露 330ml 兩入、AMIINO 安美諾 30ml、YES 腳指甲剪刀 10.5cm、YES 極細指甲緣硬皮剪刀 9cm 的安全 total-price 線,並補 ANNY / OPI 指甲油型號 code hard veto,避免不同色號被錯配。
|
||||
- V10.542 拆開「可用比價覆蓋率」與「身份覆蓋率」:`decision_ready_rate = fresh identity / ACTIVE 商品數`,Dashboard 第一張 KPI 改顯示真正可進入決策、圖表、簡報的比價資料比例;daily / growth / Webcrumbs / OpenClaw payload 同步輸出,避免把身份覆蓋、新鮮率、價格可用率混成單一數字。
|
||||
- V10.541 補正式覆核頁高信心 exact 線:The Ordinary 咖啡因 EGCG 單側漏 30ml、Natures Care 綿羊油同入數 125ml/125m、TOMOON 指甲剪同 L/S 尺寸、HH 私密潔淨露+衣物手洗精雙 200ml、SEBAMED 護潔露 200ml x2、YES 德悅氏 9cm 剪刀;都同步進 revalidation SQL,且 TOMOON/O.P.I 不同型號或尺寸仍不得自動通過。
|
||||
- V10.540 補 O.P.I 類光繚指彩精準型號線:雙方同為 O.P.I 類光繚 / 如膠似漆指甲油或指彩,且共享 `ISL...` 型號 token 時才允許 total-price;不同型號/色號仍不得自動寫入。同步把此族群接進 `true_low_confidence` revalidation 窄門,降低高信心指彩候選卡在人工覆核池的比例。
|
||||
- V10.539 補 PChome 任選 catalog focused exact 線:FLORTTE 水果沙拉眼線液筆 0.5ml、露得清護手霜 56g 無香/有香、Kanebo ALLIE 持采亮化 UV 防曬水凝乳 60g 雙方皆任選時可走 total-price;同步接進 revalidation queue。focused bypass 新增 commercial condition 防線,`即期品` 等商業狀態差異不會被自動寫入正式價差。
|
||||
- V10.538 修 ai_calls provider CHECK 對齊:Hermes/Ollama 全失敗或未選定 host 時的 `ollama_other` 只作 telemetry bucket,migration 043 放行此值,`ai_call_logger` 也會將空值/unknown/非白名單 provider 正規化,避免觀測寫入失敗。
|
||||
- V10.537 將 V10.536 focused exact 線接進 `run_retryable_candidate_revalidation()` 窄門:既有 `true_low_confidence` 舊候選若命中新品線且無 hard veto / 型別、款式、香味、件數、組合阻擋,就可重新走 matcher 寫入正式價差;有色號/香味/即期等阻擋仍不進回刷。
|
||||
- V10.536 補 PChome 高分 `true_low_confidence` 安全救回線:新增花美水 Relax 薰衣草潤滑凝膠 1.7g x3、St.Clare 私密呼呼慕斯 x2 / 慕斯+噴霧組、BIOPEUTIC 果酸煥膚水凝乳 20% 150ml、台塑生醫嬰兒沐浴洗髮 3 件組、Elizabeth Arden 八小時護唇膏 SPF15 3.7g x3、理膚寶水全面修復潤唇膏 7.5ml focused total-price 規則;這些都要求同品牌、同品線與同規格/同組合,仍保留色號、香味、款式敏感品的 `variant_selection_review` 防線。
|
||||
- V10.535 修 ElephantAlpha 價格 trigger statement timeout:`price_drop_alert` / `market_opportunity` / DB evidence prefetch 改為先篩最近有效 PChome identity_v2,再用 `JOIN LATERAL` 查單一 SKU 最新 MOMO 價格;保留 match_score/tags/diagnostic evidence,避免 scheduler 週期性重查整張 `price_records`。
|
||||
- V10.534 收緊 PChome rescore accepted gate:`no_match / price_basis=none / alert_tier=suppress` 不得再進 `rescore_accepted_current`,並新增 `--retract-unsafe-accepted` 退回舊的 unsafe accepted rows;Dashboard / daily / growth / OpenClaw 文案改為「重算待人工覆核」,避免操作員把人工覆核隊列誤解為可直接採用或可自動寫價。
|
||||
- V10.533 補 ElephantAlpha legacy OpenClaw advisory 相容:`generate_dynamic_pricing_strategy` 與既有 `generate_market_strategy` / `generate_resource_optimization_strategy` 一樣只記錄為 skipped,不再觸發 `Unrecognized step` 與 circuit breaker;避免舊協調器輸出的建議型動態定價步驟被誤解為真正可執行任務。
|
||||
- V10.532 修正 PChome coverage / review queue 口徑落差:`fetch_competitor_coverage()` 的 `attempt_status` / `rescore_accepted_count` / `actionable_review_count` 改跟 review queue 一樣統計「沒有新鮮有效 identity」的商品,而不是只看「完全沒有 identity」;這讓已過期 identity 的 `rescore_accepted_current` 待審能正確顯示在 Dashboard / 狀態 API。
|
||||
- V10.531 補 PChome matcher 過度保守的安全 exact 線:同品線、同規格、同數量的多件組若沒有 variant / count / bundle / commercial / unit-price 等阻擋理由,且商品型別完全對齊,允許進 `exact / total_price / price_alert_exact`;新增 DHC 純欖護唇膏 1.5g、FRUDIA 蜂蜜藍莓護唇膏 10g、SEBAMED 嬰兒護唇膏 4.8g x2、理膚寶水滋養修護潤唇膏 4.7ml 的 focused total-price 規則。負例仍鎖住混合組、香味款、粉底色號與蠟燭 catalog,不放寬全域門檻。
|
||||
- V10.530 輕量化 PChome 狀態 preview 並暫停 `recover-stale` 主操作入口:`_fetch_retryable_candidate_skus()` 先從最新 `competitor_match_attempts` 縮小可重評候選,再用 `JOIN LATERAL` 只取該 SKU 最新 MOMO 價,避免 `/api/ai/pchome-match/backfill/status` 因 `price_records` 全量 window scan 超時;正式 smoke 同時顯示過期 identity fresh-search rescue 5 筆耗時約 109 秒且 0 筆成功,因此 Dashboard 移除「救援過期 40 筆」按鈕,只保留 `stale_recovery_preview` 的只讀「可救援」觀測;後端 `/api/ai/pchome-match/recover-stale` 改由 `PCHOME_STALE_RECOVERY_ENABLED=true` 顯式開關才可執行,避免操作員誤按低成功率慢路徑拖住 worker。
|
||||
- V10.529 補強 `recover-stale` 名稱風險擋詞:過期 identity 搜尋救援會先排除 `+`、`x2`、`*2` 等組合暗示,以及湛藍、麋香、海洋、玫瑰、薰衣草、生理呵護、日用型、清爽、潤澤等常見變體 / 香味 / 版本詞,避免同品牌同規格但不同香味、不同膚感、不同使用情境的 stale pair 進慢速 fresh search。
|
||||
- V10.528 將 `recover-stale` 救援 preview 改成輕量雙階段篩選:SQL 從過期 `competitor_prices` 小集合出發,只做 identity_v2、過期、exact/total_price/price_alert_exact 等必要條件並限制候選量,再用 `JOIN LATERAL` 取 ACTIVE 商品最新 MOMO 價;variant / catalog / commercial condition / 高風險名稱訊號改在 Python 對小樣本過濾,避免正式站看板狀態端點因全量 price_records、JSONB + regex 過重查詢拖垮 app worker。
|
||||
- V10.527 收斂 PChome 過期 identity 搜尋救援隊列:`recover-stale` 不再直接吃全部過期 `identity_v2`,改走 `_fetch_expired_identity_recovery_skus()`,只收既有正式診斷為 `exact_identity / total_price / price_alert_exact` 且無 variant、catalog、commercial condition、count、bundle、unit-price 等阻擋理由的舊配對;名稱含任選、多款、香味、色號、即期、融燭燈、香氛蠟燭等高風險訊號也先排除,避免慢速 fresh search 把人工覆核型 stale pair 全部掃進來。
|
||||
- V10.526 將 PChome 近門檻重評池與過期 identity 搜尋救援變成可觀測、可操作產線:`preview_retryable_candidate_revalidation()` / `preview_expired_identity_recovery()` 都是 read-only,不啟動 PChome 搜尋、不呼叫 LLM、不寫 DB;`/api/ai/pchome-match/backfill/status` 回傳 `revalidation_preview` / `stale_recovery_preview`,Dashboard 顯示「可重評 / 窄門 / 可救援」數字,並新增「救援過期 40 筆」按鈕呼叫 `/api/ai/pchome-match/recover-stale`,只在舊 PChome ID 缺失或低分時走受控 fresh-search recovery,最後仍經 hard veto、auto price write safety 與 overwrite protection。
|
||||
- V10.525 補高分 review-gated exact 舊候選重評入口:`run_retryable_candidate_revalidation()` 仍以 `low_score / refresh_low_score / recoverable_low_score` 為主,只額外允許 Beauty Foot / KAMERIA / TS6 / Vaseline 這批已補 focused exact 規則、舊分數 >= 0.95、無商業狀態 / 款式 / 入數 / 組合阻擋理由的 `true_low_confidence` 進窄門重評,讓 V10.523 的安全規則可以實際回收舊資料,不把所有人工審核候選打開。
|
||||
- V10.524 將「待刷新」變成可操作入口:商品看板 PChome 補抓產線新增「刷新過期 120 筆」按鈕,呼叫 `/api/ai/pchome-match/refresh-stale` 背景執行 `run_expired_identity_refresh()`,只刷新既有 `identity_v2` 的 PChome product_id,不跑 fresh search recovery、不呼叫 LLM,完成後重算 AI 挑品並清除 Dashboard / 競價快取。
|
||||
- V10.523 補一批高分真同款 exact identity 比價規則:Beauty Foot 足膜、KAMERIA 積雪草足膜、TS6 蜜愛潤滑液 / 蜜桃煥白凝膠 / 極淨白+煥白組合、Vaseline 嬰兒高純修護凝膠在規格、入數、品牌與品線完全對齊時可進 `exact / total_price / price_alert_exact`,讓可用比價覆蓋增加;同時保留 TS6 香味衣物手洗精等 variant-sensitive 款式在 `manual_review`,不放寬全域門檻。
|
||||
- V10.522 將 PChome 補抓狀態 API 接上 read-only coverage snapshot:`/api/ai/pchome-match/backfill/status` 會同步回傳身份覆蓋、新鮮率、待刷新與待補抓數,Dashboard 補抓產線即使沒有最近任務結果,也能直接判讀下一步該刷新過期價格或補抓未搜尋商品。
|
||||
- V10.521 將比價新鮮度 stale 指標上屏:首頁 KPI / PChome 補抓產線 / daily / growth 都顯示價格過期數,讓操作員分清「已確認同款但價格待刷新」與「尚未找到身份配對」;過期 identity refresh 也優先刷新 `total_price / price_alert_exact` 的正式價差配對。
|
||||
- V10.520 拆開過期價格刷新與搜尋救援:`run_expired_identity_refresh()` 只刷新既有 `identity_v2` PChome product_id,不再因少數 product_id 查不到或低分而同步進入慢速 `fresh_search_recovery`;缺失 / 低分候選交給 `run_retryable_candidate_revalidation()` 處理,避免正式刷新 500+ 筆時被外部搜尋拖死,讓價格新鮮度可以穩定批次回升。
|
||||
- V10.519 對齊 Webcrumbs host data metadata 與新版比價覆蓋口徑:`services/webcrumbs_host_data_service.py` 會同時輸出身份覆蓋、價格新鮮、過期配對與待補抓數,讓 shared-ui plugin / 其他專案 proxy 不會把 `coverage_rate` 誤讀成價格可用率。
|
||||
- V10.518 修正 PChome 比價覆蓋率口徑與新鮮度產線:`fetch_competitor_coverage()` 改拆「身份覆蓋」與「價格新鮮」,覆蓋率不再因 `expires_at` 過期被歸零;首頁 / 業績 / 成長頁同步顯示身份覆蓋、價格新鮮數與新鮮率。PChome 快取 TTL 預設由 6h 改 48h,並把每日 expired identity refresh / retryable / unmatched limits 改為環境變數,預設提高到 1200 / 240 / 240,避免 1800+ 已配對 identity 因刷新量不足長期失效。
|
||||
- V10.517 補 PChome 近門檻比對安全 exact 與香氛 variant 防線:Lab52 齒妍堂汪汪隊嬰幼兒牙刷 2 入組可由低分區提升為 `exact / total_price / price_alert_exact`;Les nez 香氛融蠟燈不同款式、Time Leisure 香薰蠟燭單側香味款式會被留在覆核 / veto,不再進 recoverable 自動回刷,避免為了壓低 low_score 而錯配款式。
|
||||
- V10.515 補 Webcrumbs host data 硬性授權:即使正式環境 `DISABLE_LOGIN=true` 讓一般 `@login_required` 放行,`/api/webcrumbs/marketplace-host-data` 仍必須有登入 session 或 `X-Internal-Key` 才能取真實 SKU/價差;`/webcrumbs` 未授權時只注入 `auth_required` 空狀態,避免 inline seed data 公開正式比價資料。
|
||||
- V10.514 新增 Webcrumbs MOMO/PChome host data read-only API:`/api/webcrumbs/marketplace-host-data` 回傳與 `/webcrumbs` inline seed 相同的登入後 JSON contract,提供 plugin / QA / 其他專案 proxy 驗證;API boundary 明確標示不寫 DB、不呼叫 LLM、不抓外站,只允許 exact / total_price / price_alert_exact 價差摘要。
|
||||
- V10.513 外部工具診斷頁 payload 模組化:新增 `services/external_tool_payload_service.py`,把 Metabase/Grist/Webcrumbs 的診斷 payload 與 Webcrumbs host data 組裝移出 `routes/system_public_routes.py`,讓 route 回到 HTTP glue,`system_public_routes.py` 從 600+ 行降至 500 行內。
|
||||
- V10.512 Webcrumbs live plugin 接上 MOMO/PChome 只讀 host data:新增 `services/webcrumbs_host_data_service.py`,復用 `competitor_intel_repository.fetch_top_competitor_risks()` / coverage,把 exact / total_price / price_alert_exact 價差摘要轉成 `StockPlatformSharedUI.marketSnapshot` / `aiCandidate`;不呼叫 LLM、不抓外站、不寫 DB,無風險或失敗時仍輸出安全空狀態。
|
||||
- V10.511 Webcrumbs live plugin 補 host data 安全空狀態:`/webcrumbs` 會注入 `StockPlatformSharedUI.marketSnapshot` / `aiCandidate` 的診斷空資料,避免 plugin fallback demo 數字被誤認成真實市場或 AI 建議。
|
||||
- V10.510 Webcrumbs 從 runtime 接線推進到專案內 live plugin 試點:`/webcrumbs` 會設定 `StockPlatformSharedUI.allowedPluginUris` 並嵌入同源 `/webcrumbs-assets/plugins/finance.market-ticker-strip/0.1.0`、`finance.ai-candidate-card/0.1.0`,用同一頁同時驗 runtime、plugin proxy 與共享 UI loader 初始化。
|
||||
- V10.509 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval Writer Preflight 安全預覽 gate:只審核 human approval 通過後由操作員貼回的 writer preflight 摘要,確認 approval identity、writer_preflight_id、row count、dedupe keys、approved decision 到 target review_state 的逐列映射、decision/approval/preflight evidence refs、exact identity / variant / overwrite guard 與 operator boundary;API 不讀 token、不執行 CLI、不開 DB、不寫 preflight/approval/decision/match、不更新 review_state、不補 queue、不掛 scheduler,只放行到後續 CLI review / run package 設計。
|
||||
- V10.508 Webcrumbs 轉為 momo-pro 同源 asset proxy:`WEBCRUMBS_RUNTIME_URL` 預設改為 `/webcrumbs-assets/loader/webcrumbs-compatible-loader.js`,新增 allowlist proxy 只代理 188 Shared UI Hub 的 `loader/`、`plugins/`、`demo/`,避免 `webcrumbs.wooo.work` 公網 TLS / Basic Auth 尚未收斂時影響正式頁面載入;`/webcrumbs` 診斷頁同步顯示 asset upstream。
|
||||
- V10.507 接入 Webcrumbs 共用 UI Runtime:新增 `WEBCRUMBS_*` 環境設定與 `/webcrumbs` 診斷入口,`ewoooc_base.html` 會在 runtime 啟用且 URL 有效時載入自架 Webcrumbs script;側欄新增 Webcrumbs 入口,並補 `docs/guides/webcrumbs_shared_runtime.md` 作為跨專案接入手冊。生產仍需固定版本與自架 plugin URI,不使用官方 `@latest`。
|
||||
- V10.506 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval 安全預覽 gate:只審核 review decision 通過後由操作員貼回的人工 approval 摘要,確認 approval identity、row count、dedupe keys、approved decision、evidence refs、exact identity / variant / overwrite guard 與 operator boundary;API 不讀 token、不執行 CLI、不開 DB、不寫 approval/decision/match、不更新 review_state、不補 queue、不掛 scheduler,只放行到後續人工 approval writer preflight 設計。
|
||||
- V10.505 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision 安全預覽 gate:只審核 review inventory 通過後由操作員貼回的人工 candidate queue review decision 摘要,確認 decision identity、target table、row count、dedupe keys、`needs_review` 現態、允許決策集合、evidence refs、matched row exact-identity/variant/overwrite guard、operator confirmation 與 forbidden API actions;API 不讀 token、不執行 CLI、不開 DB、不寫 decision record、不更新 review_state、不寫 match result、不補 queue、不掛 scheduler。UI 同步新增 Decision gates / Inventory link / Decision summary / Decision rows / Boundary next 預覽區。
|
||||
- V10.504 新增市場情報 MCP Fetch Candidate Queue Writer Review Inventory 安全預覽 gate:只審核 writer review handoff 通過後由操作員貼回的 read-only candidate queue inventory 摘要,確認 handoff identity、target table、row count、dedupe keys、review_state、artifact paths、read-only query result、missing/duplicate rows、operator confirmation 與 forbidden API actions;API 不讀 token、不執行 CLI、不開 DB、不寫 queue、不更新 review_state、不做 inventory query、不掛 scheduler。主 gate 拆為 inventory / gates / sample 三檔,避免單檔膨脹。
|
||||
- V10.503 新增市場情報 MCP Fetch Candidate Queue Writer Review Handoff 安全預覽 gate:只審核 post-closeout inventory review 通過後的人工 candidate queue review 交接包,確認 inventory linkage、handoff identity、target table、row count、artifact paths、review contract、forbidden API actions 與 operator confirmation;API 不讀 token、不執行 CLI、不開 DB、不寫 queue、不更新 review_state、不掛 scheduler。
|
||||
- V10.502 修正 AiderHeal 自動修復診斷鏈:先做 ADR-020 檔案白名單再打 110 preflight,`tests/` finding 會明確略過而不誤報 repo preflight;Code Review 完成通知會把全數不在白名單的 finding 標成需人工處理,不再宣稱已觸發 AiderHeal;白名單放行 `services/routes/database` 子目錄 Python 檔,preflight 通知帶 stderr/stdout 細節,健康檢查同時接受 `/health` 回 `ok` 與 `healthy`。
|
||||
- V10.501 新增市場情報 MCP Fetch Candidate Queue Writer Post-Closeout Inventory Review 安全預覽 gate:只審核 closeout review 後由操作員 shell 完成的 live inventory read-only 摘要,確認 closeout linkage、row count、inventory artifact、closeout review artifact、read-only query result、missing/duplicate rows 與 operator confirmation;API 不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 inventory query、不掛 scheduler。
|
||||
- V10.500 新增市場情報 MCP Fetch Candidate Queue Writer Run Closeout Review 安全預覽 gate:只審核 receipt review 通過後的 operator closeout 摘要,確認 receipt linkage、closeout artifact、receipt review artifact、post-closeout inventory plan、writer output / post-write smoke / backup manifest、rollback note 與 operator confirmation;API 不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-closeout query、不掛 scheduler。
|
||||
- V10.499 新增市場情報 MCP Fetch Candidate Queue Writer Run Receipt Review 安全預覽 gate:只審核操作員 shell writer run 後貼回的 receipt 摘要,確認 readiness linkage、run package id、候選/dedupe keys、writer output、post-write smoke、backup path 與 operator confirmation;API 不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-write query、不掛 scheduler。
|
||||
- V10.498 新增市場情報 MCP Fetch Candidate Queue Writer Run Readiness 安全預覽 gate:只審核 run package review 後的操作員 readiness 證據,確認 run readiness artifact、reviewed sample、備份、read-only preflight 與 post-write smoke 路徑安全,以及 CLI-only / approval token shell-only 邊界;API 不產檔、不讀 token、不執行 CLI、不開 DB、不寫 queue、不掛 scheduler。
|
||||
- V10.497 新增市場情報 MCP Fetch Candidate Queue Writer Run Package Review 安全預覽 gate:只審核 CLI review 通過後的 operator run package 草案,要求 package id、artifact manifest、operator shell command sequence、candidate/dedupe keys 與 CLI review 對齊;API 不產檔、不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不掛 scheduler,只放行到 run readiness review。
|
||||
- V10.496 新增市場情報 MCP Fetch Candidate Queue Writer CLI Review 安全預覽 gate:只審核 writer preflight 後的 CLI review 草案,確認 script path、target table、preflight id、payload row count、candidate/dedupe keys 與 command argv;禁止 API 執行 CLI、禁止 `--execute` / `--apply-real-write` / `--approval-token` 進 payload,API 不讀 token、不寫檔、不開 DB、不寫 queue、不掛 scheduler。
|
||||
- V10.495 新增市場情報 MCP Fetch Candidate Queue Writer Preflight 安全預覽 gate:只審核 queue review 後的 writer preflight 草案,確認 target_table、write_mode、dedupe strategy、insert columns、payload rows 與候選 key 對齊;API 不開 DB、不執行 CLI、不建立 queue、不更新 review_state、不寫 DB、不連外、不掛 scheduler。
|
||||
- V10.494 新增市場情報 MCP Fetch Candidate Queue Review 安全預覽 gate:只審核 candidate handoff 後的人工 queue review 草案,要求候選 key 對齊、review_state 停在 needs_review、allowed actions 限定人工確認/否決/延後、queue_write_status 維持 not_persisted;API 不建立 queue、不更新 review_state、不寫 DB、不連外、不掛 scheduler。
|
||||
- V10.493 新增市場情報 MCP Fetch Candidate Handoff Review 安全預覽 gate:只審核 parser review 後的候選交接包,確認 source/candidate key 對齊、queue policy 仍是 manual preview、候選數維持小批次、無 raw/secret/side-effect;API 不建立 queue、不寫 DB、不讀 artifact、不連外、不掛 scheduler。
|
||||
- V10.492 收緊 PChome 近門檻自動回刷隊列:`retryable_candidate_revalidation` 不再把 `identity_veto`、`unit_comparable`、`true_low_confidence` 納入每日自動回刷;只處理 `recoverable_low_score` 與 legacy `low_score / refresh_low_score`,並要求無 hard veto、仍在 `exact_identity`、且具備同品線/identity anchor 證據。這讓「可救回」與「正確阻擋」在操作層面真正分流,避免為了壓低 low_score 而重跑不該自動推進的候選。
|
||||
- V10.491 新增市場情報 MCP Fetch Result Parser Review 安全預覽 gate:只審核操作員貼回的 parser 結構化摘要,對齊 receipt source/path、候選必要欄位、公開 URL、小批次上限與 raw HTML/secret/side-effect 風險;API 不讀 artifact、不執行 parser CLI、不抓外站、不寫 DB、不掛 scheduler。
|
||||
- V10.489 補 PChome 低分同款人工覆核回收與 gate-pass 風險邊界:TS6 超美白香氛誘霜 120g/ml、W 修護保養蝸牛特潤修護面膜 6 片、Derma 大地 Eco 植萃護膚油 2 入,從低信心升成 `identity_review` 人工覆核候選;Clarins 輕盈美體護理油 vs 身體調和護理油、台塑生醫嬰兒沐浴/洗髮組合數量反轉、isLeaf 私密慕絲香型數量不一致改 hard veto;HOOOME 大理石暖燈 vs 泛稱經典款改留 `variant_selection_review`。正式價差表仍需人工採用才會寫入。Production 已部署 `/health=V10.489`;500 筆 read-only audit 由 V10.486 基線 `gate_pass=129 / identity_veto=1 / still_low=370` 收斂為 `gate_pass=124 / identity_veto=4 / still_low=372`。測試:完整 `pytest` 1289 passed / 9 skipped。
|
||||
- V10.488 新增市場情報 MCP Fetch Run Receipt 安全預覽 gate,只審核操作員 dry-run receipt,不執行 CLI、不抓外站、不寫 DB。
|
||||
- V10.486 補 PChome near-threshold 風險邊界:NEW DIRECTIONS 甜杏仁油 vs 酪梨油直接 `core_ingredient_line_conflict` hard veto;COCODOR 經典擴香瓶多款任選 vs generic、KAMERIA 足膜任選三款 vs 單一涼感足膜、Hakugen 白元入浴劑橘盒/綠盒不同變體都保留 `variant_selection_review`,不進可採用 gate。Production 已部署 `/health=V10.486`;240 筆 near-threshold audit `gate_pass 83→79`、`identity_veto 0→1`、`still_low 157→160`。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。
|
||||
- V10.485 補 NITORI 香氛噴霧器短型號防線:read-only near-threshold pilot 找到唯一 gate pass 為 5510 vs J82 LBR,不應入隊;matcher 現在會把 `J82` 這類短英數型號納入 NITORI diffuser model conflict,與 5510 / YX168 等不同型號一樣 hard veto。Production 已部署 `/health=V10.485`;120 筆 near-threshold audit 由 `gate_pass=1` 變 `gate_pass=0`,accepted audit `scanned=89 / gate_pass=89 / still_low=0`。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。
|
||||
- V10.484 拆分 PChome manual gate:POWERMAN 男性私密養護液 30ml、PHYSIOGEL AI 冰鎮精華露 200ml 2入、TS6 緊彈水嫩凝膠 40g、DERMA 寶寶洗髮沐浴露 150/500ml、Clarins 黃金亮眼萃 20ml、Cetaphil 長效潤膚乳 237/473ml 等明確同款可走 `exact / total_price / price_alert_exact`;COCODOR 大豆蠟燭單側多款任選改留 `variant_selection_review`,Pavaruni 雙側 20 香味蠟燭不受新型錄保護誤傷。Production 曾部署 `/health=V10.484`,並退回 COCODOR 舊 accepted 風險 1 筆。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。
|
||||
- V10.483 收斂舊 gate pass 風險:NARS 遮瑕蜜任選、LOREAL 玻尿酸啵啵精華水/液態紫熨斗 vs 水光精華、SEBAMED 洗髮乳任選、Schick 舒綺 2-in-1 型號落差、TAICEND 保護膜 vs 噴霧,現在都會保留高分但加 `variant_selection_review` 與專屬 reason,不再被 rescore 自動送進 accepted queue。Production 已部署 `/health=V10.483`;目標 5 SKU audit `gate_pass=0 / still_low=5`,並用 `--retract-variant-accepted` 退回 4 筆舊 accepted 變體風險,latest accepted audit `scanned=90 / gate_pass=90 / still_low=0`。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。
|
||||
- V10.482 補 exact variant-safe 回收:LUSH 櫻之花身體噴霧 200ml、ARTMIS 金縷梅/蔓越莓私密清潔慕斯 250ml、SO NATURAL FIXX 120ml plain 與 Baan 原味/草莓同 catalog,若雙方同品名、同規格且同明確 variant,移除過度保守的 `variant_selection_review` 並進 `exact / total_price / price_alert_exact`;SO NATURAL 經典款/光澤款/霧面款/夏日款 catalog 對單款 120ml 仍維持人工覆核。Production 已部署 `/health=V10.482`,並只 materialize 5 筆新增 exact-line SKU 到 `rescore_accepted_current`,最新 accepted audit `scanned=94 / gate_pass=94 / still_low=0`。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。
|
||||
- V10.481 補 rescore accepted retraction 工具缺口:`--retract-variant-accepted` 不只看舊 row 已存的 `diagnostic_codes`,也會用當前 matcher 重判 latest `rescore_accepted_current`;若新版規則已變成 `variant_selection_review / low_score_current`,會追加退回 `true_low_confidence`,避免舊 accepted queue 殘留不該採用候選。Production 已先保守 materialize 15 筆安全 SKU,再退回 7 筆舊 accepted 變體風險;最終 `rescore_accepted_current=89`,accepted audit `gate_pass=89 / still_low=0`。
|
||||
- V10.480 依 production accepted-current 風險樣本補安全閘門:rom&nd 零絲絨/果凍唇釉不可被果汁唇釉多款 listing 誤收為同款;Relove 潔淨凝露若一側為傳明酸/淨白活性變體改送 `variant_selection_review`;1990 融燭燈不同設計(歐式可彎 vs 韓風原木底座)改 hard veto。此版先清 accepted queue 風險,再做保守 materialize。
|
||||
- V10.479 依 production audit 再補二階風險:Cetaphil 修護乳 vs 潔膚露改 hard veto;私密防護慕絲二款可選 vs 單一香型、雪芙蘭滋養霜 vs 單側清爽型改走 `variant_selection_review`,避免仍殘留在 accepted queue。
|
||||
- V10.478 補 PChome 高分錯配 / catalog 變體防線:精油/香氛類若兩側明確香味不同(如檸檬草 vs 茶樹)直接 veto;NOW 椰子油膏 vs 乳木果油、港香蘭漢本 vs 艾魔菈爽身粉改為商品線硬擋;多色/多香/數字區間 catalog 對單一款式(粉餅盒、眉筆、眼線膠筆、車用擴香蕊等)只進 `variant_selection_review`,不自動進 accepted queue。
|
||||
- V10.477 補 PChome 高分錯配防線:SPF 數值不同(如 SPF25 vs SPF50)直接 veto;MAKE UP FOR EVER 定妝噴霧 vs 活氧水不同線直接 veto;多款任選對單一款(私密潔浴露、身體去角質、乳液、染眉膏等)與單側色號改送 `variant_selection_review`,避免高分候選誤入 accepted queue。
|
||||
- V10.476 補 PChome 商品比對「商業條件差」防線:即期品、效期/保存期限、盒損、福利品等條件若只出現在單側,matcher 會加 `commercial_condition_gap` + `variant_selection_review`,保留高分但不讓 rescore 自動進 accepted queue。這可避免 3W CLINIC 粉底液、KAMERIA 足膜、Sisley 全能乳液這類同名但商品狀態不同的候選被當成一般正品價差。
|
||||
- V10.475 補 PChome rescore 操作與高分錯配防線:`scripts/audit_competitor_match_attempt_rescore.py` 預設不再只掃 `strong_exact_spec_match`,避免漏掉 `focused_exact_*` 等新版 matcher 理由;matcher 新增暖燈 S/M/L 尺寸差、NITORI 香氛噴霧器型號差的 hard veto,並把彩妝色號單邊出現的高分候選送進 `variant_selection_review`,避免 LA MER 氣墊等色號型商品被誤入 accepted queue。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。
|
||||
- V10.474 補 PChome near-threshold matcher / feeder 下一階段:新增 HOOOME 白色經典香氛暖燈與 Gdesign Aroma Lava 2.0 的窄範圍 total-price exact 回收;Recipe Box 可撕式水性兒童指甲油只進 identity_review,不自動寫正式價差;Pavaruni 蠟燭 vs 精油、DASHING DIVA 不同款式仍維持 veto/低信心。known-id refresh 現在會對 hard-veto 舊候選執行 fresh search recovery,missing known-id 若 fresh search 只找到低分候選也會保留 best candidate + diagnostics,而非落成 `refresh_no_result`;正式覆寫保護新增 stronger existing guard,避免較弱新候選以高分覆蓋既有強正式配對。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。
|
||||
- V10.473 補背景 embedding host_health skip:`allow_111_fallback=false` 會讀最近 `host_health_probes`,跳過 runtime unhealthy 的 GCP 節點(預設 20 分鐘,DB fail-open),避免每筆任務都等待已知壞節點 timeout;路由安全不變,不把背景 embedding 落 111。
|
||||
- V10.472 補 GCP Ollama failover rootless 診斷:新增 `scripts/ops/diagnose_ollama_gcp_failover.sh`,可一鍵檢查 GCP-A direct、GCP-B direct、111、110:11435、110:11436 與 GCP-B `bge-m3` runtime;目前輸出確認 GCP-A `11434` refused、GCP-B direct/embed OK、110:11435 502、110:11436 OK。110 無免密 sudo、GCP-A 22 refused、GCP-B SSH key denied,因此 primary 修復需 GCP/SSH 或 110 root 權限;應用層維持 GCP-A → GCP-B → 111,不把背景 embedding 落 111。
|
||||
- V10.471 依 GCP-B `bge-m3` 實測 latency 調整 embedding timeout,已部署正式環境並確認 `/health=V10.471`:GCP-B `/api/embed` 三次實測約 6.4s / 7.3s / 23.5s,原本 `OLLAMA_EMBED_MAX_TIMEOUT=15` 與 host health `8s` 會誤殺慢但成功的 embedding;已將背景 embedding cap 與 host health model probe timeout 預設調為 30s。正式 smoke 顯示容器內 embedding 回 1024 維、耗時 10.07s;手動 host health probe 後最新狀態為 GCP-A unhealthy、GCP-B healthy、111 healthy。路由安全不變:背景 embedding 仍只跑 GCP-A/GCP-B,不落 111。
|
||||
- V10.470 強化 Ollama host health probe,已部署正式環境並確認 `/health=V10.470`:scheduler 與觀測台 host health 對 GCP-A / GCP-B 除 `/api/tags` 外,再做短 `bge-m3` `/api/embed` 實作探針;可抓出 GCP-B「tags/version 正常但 embedding runner 8s timeout」這類假健康。111 預設不做背景 embedding probe,避免監控任務把 `bge-m3` 載入 fallback Mac。正式 smoke 後 `host_health_probes` 最新狀態為 GCP-A unhealthy、GCP-B unhealthy、111 healthy。
|
||||
- V10.469 將背景 embedding 的 GCP-only 全失敗改為專業降級語意,已部署正式環境並確認 `/health=V10.469`:`allow_111_fallback=False` 時若 GCP-A/GCP-B 都不可用,開啟 failure circuit 並記 WARNING,不再把可預期的背景熔斷每分鐘打成 ERROR;同步 / 允許 fallback 的 embedding 全失敗仍保留 ERROR。Smoke 顯示 GCP-B `/api/version` 可用,但 `/api/embed` 仍可能 15s timeout,下一步需修 GCP-A primary 與 GCP-B runner/model 負載。
|
||||
- V10.468 補 Ollama import-time 防凍結與背景 embedding GCP failure circuit,已部署正式環境並確認 `/health=V10.468`:`config.OLLAMA_HOST` / `HERMES_URL` / `EMBEDDING_HOST` 舊相容常數不再於 import 時 probe network,也不會因 GCP-A/GCP-B 暫時拒連而 freeze 到 111;動態 caller 仍走 `get_*()` / `OllamaService` 三主機級聯。當 `allow_111_fallback=False` 且 GCP-A/GCP-B 皆失敗時,短暫熔斷 60 秒,不重複打兩台 GCP、不落 111,降低 app/scheduler 因連續 embedding timeout 造成的 log 與 worker 壓力;部署 smoke 時 GCP-B `/api/version` 已恢復 200 並成為動態路由落點,GCP-A `22/11434` 仍拒連,需後續用 GCP 權限修復 primary Ollama 主機。
|
||||
- V10.467 補 PChome focused exact total-price 安全通道:針對正式近門檻樣本中已確認同品牌、同品名、同規格/同入數的 3W CLINIC 粉底液 2入、花美水凝膠 3支、The Ordinary 咖啡因 EGCG 30ml、KUSSEN 屁屁膏 3入、Bone 擴香禮盒、1990 融燭燈白色款與 CANMAKE 淚袋盤,從 `exact/manual_review` 收斂為 `exact/total_price`;未放寬 `MIN_MATCH_SCORE`,DASHING DIVA、唇彩、香味、色號/款式敏感商品仍維持 variant / veto 保護。Production pilot 已將 9 筆安全 SKU 送入 `rescore_accepted_current`,`true_low_confidence` 802→793、`rescore_accepted_current` 38→47;`6101784` 即期品保留在 `true_low_confidence`。
|
||||
- V10.466 修正 rescore audit duplicate 判斷:只在「最新 attempt 已是同候選 `rescore_accepted_current`」時跳過;若歷史曾 accepted、但後續 crawler 又追加低信心列,允許重新 materialize,避免 Dashboard latest-state 仍停在 `true_low_confidence`。Production pilot 已將 SKU `14756069`、`11159042`、`13842560`、`8394210`、`15192547`、`10509765`、`10603780` 送入人工覆核隊列;只寫 `competitor_match_attempts`,`competitor_prices` / `competitor_price_history` 未變。
|
||||
- V10.465 修正 embedding fallback-disabled 控制流:`allow_111_fallback=False` 時若 resolver 回 111,不再直接退出或只試單台 GCP-B,會強制改試尚未嘗試的 GCP-A/GCP-B;背景 embedding 仍不落 111。
|
||||
- V10.464 補 rescore audit 精準 SKU pilot:`audit_competitor_match_attempt_rescore.py --sku` 可只掃指定 SKU,再搭配 `--apply-accepted` 只把通過新版 matcher 的目標 SKU 追加到 `rescore_accepted_current` 人工覆核隊列,不寫正式價格表。
|
||||
- V10.463 補 DR.WU / 達爾膚品牌 alias:同規格 `DR.WU 達爾膚` 與 `DR.WU` 候選不再被當成 brandless identity review,會以既有 exact_identity / total_price / price_alert_exact 閘門處理;未調整 `MIN_MATCH_SCORE`,保留 variant / hard veto 保護。
|
||||
- V10.462 進一步收斂 PChome 補抓 UI 語意:Dashboard 區塊標題改為「PChome 補抓產線」,AI 中樞按鈕、前端確認與 API 訊息改為「補抓未搜尋 / 未搜尋補抓」,避免操作員把尚未搜尋的工作誤判成已有候選待審。
|
||||
- V10.461 修正商品看板 PChome 補抓優先清單的狀態語意:尚未進入搜尋/補抓的品項改顯示「尚未搜尋」與「尚未進入 PChome 補抓」,並補前端守門測試禁止回退成籠統「待比對」,避免操作員把未搜尋誤判成已有候選待人工覆核。
|
||||
- V10.460 收斂 daily/growth 圖表空白誤判與 ElephantAlpha 告警信封:`page-daily-sales.js`、`page-growth.js` 的 chart 判斷改為至少有一個非零資料點才繪製 Chart.js,避免全 0 序列只畫座標軸;`resource_optimization` / `ea_escalation` 改輸出 deterministic `decision_envelope`,只使用 action_plans、CPU 實測與 hygiene evidence,不再輸出空泛「48 小時效益」敘事。
|
||||
- V10.459 強化 PChome `protected_existing_match` 決策封包:解析 `existing_match_conflict` 的既有候選、新候選與雙方 score,寫入 `decision_envelope.evidence` / `expected_impact` / `guardrails`,並把下一步明確標成「比較既有正式候選與新候選」;仍保持 `can_auto_execute=false`,避免新候選分數較高時繞過人工覆核自動覆蓋正式價差。
|
||||
- V10.458 將 OpenClaw / 競品 PPT 接上 PChome 覆核 `decision_envelope` 摘要:`competitor_intel_repository.summarize_review_decision_envelopes()` 成為共用 formatter,OpenClaw 週報/日報/月報與競品簡報 data_summary / KPI slide 都讀同一份信封文字,避免策略報告與 PPT 各自翻譯覆核狀態或遺失 HITL guardrails。
|
||||
- V10.457 將 PChome 覆核 `decision_envelope` 連到人工操作面:Dashboard 覆核卡新增決策等級、資料品質、HITL/trace 信封摘要;`/api/export/excel/pchome-review` 匯出同步增加決策信封 ID、決策類型、建議代碼、責任人、資料品質、自動執行允許與證據摘要,讓線上操作與下載檔都保留同一份 guardrails。
|
||||
- V10.456 將 PChome 覆核隊列接上 `decision_envelope` contract:`fetch_competitor_review_queue()` 與 `/api/pchome-review/queue` 每筆候選都輸出同一份 SKU、PChome 候選、match evidence、recommended_action、expected_impact 與 HITL guardrails,Dashboard、Agent、Telegram、PPT 後續不得再各自重建比價判讀格式;同版將 review queue cache key 升到 v3,避免正式環境沿用舊 payload。
|
||||
- V10.455 讓 EventRouter 對 `decision_envelope` 事件走直送證據模板:NemoTron / 價格比對已產生 SKU、PChome 候選、match evidence 與 HITL guardrails 時,不再進 L1/L2 AI 重新摘要,避免額外模型呼叫與告警文字二次發散;Telegram 決策信封同步補「標的」區塊,顯示 SKU、商品與 PChome 候選。同版補 `audit_competitor_match_attempt_rescore.py --retract-variant-accepted`,可把最新仍帶 `variant_selection_review` 的 `rescore_accepted_current` 批次追加退回 `true_low_confidence`,且不寫正式價差表。
|
||||
- V10.454 補 feeder / rescore 正式寫入安全閘門:matcher 若只到 `manual_review` / `identity_review` / `variant_selection_review`,例如 MOMO 多款任選唇膏對 PChome 單一款式,只能進 `true_low_confidence` 覆核,不得由 retryable replay、known identity refresh 或 rescore accepted 語意自動寫入 `competitor_prices` 正式價差。
|
||||
- V10.453 補 PChome matcher 安全回收規則:新增 Herbacin 小甘菊護手霜 20ml brandless 同款 anchor;修正 `EX8` 型號不可被誤解析成 `x8` 入數;新增 GONESH / 香氛固體凝膠的一側泛稱、一側明確香味或 No. 款式 veto,避免近門檻 replay 把不同香味、不同入數商品錯寫成正式價差。
|
||||
- V10.452 修正 PChome rescore audit 掃描口徑:`audit_competitor_match_attempt_rescore.py` 預設先取每個 SKU 最新 attempt,再套用 status / reason 篩選,和 Dashboard review queue 的最新狀態一致;舊 SKU/候選考古掃描需明確加 `--include-historical-candidates`,避免已修正或已入隊商品被舊低信心紀錄重複推回報表。
|
||||
- V10.451 拆分 PChome `low_score` 操作分流並補 read-only queue API:比價覆核頁把近門檻可救、證據不足、低信心舊候選拆成獨立篩選;repository 同步提供 `recoverable_low_score`、`true_low_confidence`、`legacy_low_score` 三個 status filter,`/api/pchome-review/queue` 可直接用同一套 review_status 做 smoke / operator tools 查詢,讓回刷、人工覆核與報表不再把所有低信心候選混在一起。
|
||||
- V10.450 補 PChome 覆核 fast-count UI 語意與重算可採用指標:預設全量覆核頁跳過 exact count 時,模板會顯示「約」作為快取總數提示;搜尋、分類、單一狀態仍是精準總數。`fetch_competitor_coverage()` 同步輸出 `rescore_accepted_count`,讓 Dashboard、daily/growth 與 OpenClaw 摘要能把「重算可採用待審」從一般覆核隊列拆出來。
|
||||
- V10.449 修正 PChome 覆核 exact count 條件:只有預設「全部覆核、無搜尋、無分類」頁跳過 exact count;只要有搜尋詞、分類篩選或單一 review status,就保留精準總數,避免分頁資訊失準。
|
||||
- V10.448 讓 PChome 覆核「全部」頁跳過 exact count:`review_status=all` 使用 shared overview cache 的待處理總數作為分頁總數提示,只查當頁 50 筆;單一狀態分流仍保留 exact count,降低全量覆核頁互動成本。
|
||||
- V10.447 反轉 PChome 覆核頁查詢方向:review queue page 先從最新 `competitor_match_attempts` 的可覆核狀態縮小候選,再 join ACTIVE 商品與最新價,並用 `NOT EXISTS` 排除已有有效 identity_v2 正式價;避免每次「全部覆核」先掃全站 ACTIVE 商品。
|
||||
- V10.446 修正 PChome 覆核頁輕量路徑的 overview timeout:覆核頁總覽改讀已存在的 shared dashboard cache / stale cache,沒有快取時只用目前覆核頁資料補足狀態,不再現場跑 `_load_competitor_decision_overview(session)` 的重型後備 SQL。
|
||||
- V10.445 補 PChome 覆核頁輕量渲染路徑:`filter=pchome_review` 不再先建完整 Dashboard `unique_items`,改為只查覆核當頁 50 筆商品、當前價、昨日價與週前價,再沿用同一張新版表格與人工覆核按鈕;降低核心比價覆核頁的全站資料負載。
|
||||
- V10.444 瘦身 PChome 覆核頁查詢:`fetch_competitor_review_queue_page()` 將覆核隊列總數與當頁資料合併在單一 SQL 內取回,避免 `/?filter=pchome_review` 為 count/page 重複掃 `latest_momo`、`latest_attempt`、`valid_competitor` CTE;保留狀態分流、人工覆核與正式價格寫入保護不變。
|
||||
- V10.443 補 PChome rescore 人工覆核入隊:`audit_competitor_match_attempt_rescore.py --apply-accepted` 只追加 `rescore_accepted_current` attempt 進人工覆核隊列,不直接寫 `competitor_prices` / `competitor_price_history`;商品看板新增「重算可採用」分流與狀態文案,讓可救回候選先由人審確認再正式更新價差。
|
||||
- V10.442 降噪 `/cicd` 舊 GitLab 探測:沒有明確啟用 `GITLAB_ENABLED=true` 與 token 時,不再打退役的 `192.168.0.110:8929` 或 SSH fallback,正式 responsive smoke 造訪 `/cicd` 只呈現空 pipeline 狀態,不污染 app logs。
|
||||
- V10.441 補 PChome matcher re-score audit 與商品看板原因標籤:新增 read-only `competitor_match_attempt_rescore_audit` / `scripts/audit_competitor_match_attempt_rescore.py`,可用最新版 matcher 重新分類既有 `competitor_match_attempts`,預設不寫 DB、不更新正式價格;商品看板同步補蘭蔻/達特醫/hoi/Saugella/Lactacyd 等 focused matcher reason 中文標籤,讓「待對比」能拆成商品線不符、款式版本不符、可回刷或仍低信心。
|
||||
- V10.439 收斂外部 BI / 資料協作入口:`/metabase`、`/grist` 正式頁維持 momo-pro 內部診斷 bridge,`.env.example` 與 bi profile Grist 預設改回 `https://mo.wooo.work/grist` / `GRIST_APP_HOME_URL`,並補測試禁止 `grist.wooo.work` / `awoooi` 回流到導覽設定;外部工具頁標題字級改用新版 token 與手機 media query。
|
||||
- V10.414 補市場情報 MCP fetch run readiness gate:新增 `mcp_fetch_run_readiness` read-only builder、GET/POST endpoint、UI run readiness 審核面板與 deployment readiness smoke target,在 run package 後檢查 command preview、receipt path、artifact path、節流/timeout/dry-run-first 與操作員 shell-only 邊界;API/UI 不執行 CLI、不抓外站、不寫檔、不開 DB、不掛 scheduler,只放行到人工 shell dry-run 與後續 receipt gate。
|
||||
- V10.412 補市場情報 MCP fetch run package gate:新增 `mcp_fetch_run_package` read-only builder、route extension、GET/POST endpoint、UI run package 審核面板與 deployment readiness smoke target,將已通過的 target review 轉成操作員可覆核的 command argv preview 與 receipt path 契約;API/UI 不執行 CLI、不抓外站、不寫檔、不開 DB、不掛 scheduler,只放行到後續 run readiness review。
|
||||
- V10.409 補市場情報 MCP fetch target review gate:新增 `mcp_fetch_target_review` read-only builder、GET/POST endpoint、UI target review 審核面板與 deployment readiness smoke target,讓 manual fetch handoff 通過後,先人工審核 adapter registry 公開入口、每平台節流、樣本數、timeout 與 rollback plan;API/UI 不保存 payload、不發外部 request、不開 DB、不寫入、不掛 scheduler,也不會自動打開 manual fetch。
|
||||
- V10.405 補市場情報 MCP manual fetch handoff gate:新增 `mcp_manual_fetch_handoff` read-only builder、GET/POST endpoint、UI handoff package 審核面板與 deployment readiness smoke target,將 runtime promotion package 與操作員安全確認合併成「可進人工 fetch gate 審核」的交接收據;API/UI 不保存 payload、不打 health、不開 DB、不抓外站、不掛 scheduler,也不會自動打開 manual fetch。
|
||||
- V10.379 補市場情報 MCP runtime promotion gate:新增 `mcp_runtime_promotion` read-only builder、GET/POST endpoint、UI promotion package 審核面板與 deployment readiness smoke target,將 MCP activation evidence 與 runtime smoke receipt 合併成可審核的 runtime promotion package;API/UI 不保存 payload、不打 health、不開 DB、不抓外站、不掛 scheduler,也不會自動打開人工 fetch gate。
|
||||
- V10.366 補市場情報 MCP runtime smoke 收據審核:新增 `mcp_runtime_smoke_receipt` read-only builder、GET/POST endpoint、UI receipt JSON 審核面板與 deployment readiness smoke target,讓操作員貼上 `/api/market_intel/mcp_readiness?execute=true&timeout=3` 的實際收據後,判斷 external/internal MCP runtime 是否可升級為已驗收;API/UI 不保存 payload、不打 health、不開 DB、不抓外站、不掛 scheduler,且會阻擋任何 DB write/commit/scheduler/writes 旗標或原始 readiness blocked reasons。
|
||||
- V10.360 收斂瀏覽器自動開啟與 PChome 熱路徑:`tests/test_image_fetch.py` 改成 `RUN_MOMO_BROWSER_TESTS=1` 才會跑,預設 headless 且關閉 Chrome 密碼管理,避免一般 pytest 自動打開 MOMO 網站與觸發密碼允許提示;scheduler Selenium 同步關閉 password manager/autofill;PChome coverage/review queue 熱查詢改用 `JOIN LATERAL` 取 active 商品最新價,並補 Dashing Diva 品線召回搜尋詞。
|
||||
- V10.359 導入 Browse.sh 可選爬蟲診斷與強化 MOMO/PChome 色號比對:新增 `BrowseShTool` wrapper、probe CLI 與操作手冊,讓 browse.sh 只作 selector/XHR/network trace 探勘、不進正式 scheduler;matcher 補護甲油/洗手慕斯/足膜精準搜尋詞,保留小數規格,並對唇釉、妝前乳、素顏霜等顯性色號/色系不一致候選做 hard veto,避免同系列不同色號污染正式價差。
|
||||
- V10.358 補市場情報 MCP 啟用證據審核:新增 `mcp_activation_evidence` read-only builder、GET/POST endpoint、UI redacted evidence 審核面板與 deployment readiness smoke target,讓操作員貼上 env/health/router/telemetry/fallback 證據後判斷能否補齊 external/internal MCP runtime 缺口;API/UI 不保存 payload、不打 health、不啟動 MCP、不執行 docker/SSH、不開 DB、不抓外站、不掛 scheduler,且會阻擋真實 secret 字串與任何 DB write/fetch/scheduler 證據。
|
||||
- V10.357 補市場情報 MCP 完整度稽核:新增 `mcp_completion_audit` read-only builder、GET endpoint、UI 面板與 deployment readiness smoke target,彙整外部 MCP design/runtime、內部 tool contract/runtime、activation runbook 與 fetch gate 狀態;API/UI 不啟動 MCP、不打 health、不執行 docker/SSH、不開 DB、不寫檔、不抓外站、不掛 scheduler。
|
||||
- V10.356 補市場情報 candidate queue review AI summary Telegram dispatch report catalog record final closeout gate:新增 read-only report catalog record final closeout builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 archive summary gate 後覆核 catalog record identity、artifact traceability、sections、DB commit/post-write smoke、pipeline complete 與無後續 follow-up;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 review_state、不掛 scheduler。
|
||||
- V10.355 補市場情報 candidate queue review AI summary Telegram dispatch report catalog record archive summary gate:新增 read-only report catalog record archive summary builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 archive gate 後整理 catalog record identity、artifact traceability、DB commit/post-write smoke、archive manifest/retention policy 與後續 final closeout separate gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 review_state、不掛 scheduler。
|
||||
- V10.354 補市場情報 candidate queue review AI summary Telegram dispatch report catalog record archive gate:新增 read-only report catalog record archive builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 closeout gate 後審核 closeout/commit/run receipt/writer output/post-write smoke/backup 封存證據、archive manifest/retention policy 與後續 archive summary separate gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 review_state、不掛 scheduler。
|
||||
- V10.353 補市場情報 candidate queue review AI summary Telegram dispatch report catalog record closeout gate:新增 read-only report catalog record closeout builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 commit gate 後審核 catalog record identity、DB commit/post-write smoke 證據、操作員 closeout 確認與後續 archive separate gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 review_state、不掛 scheduler。
|
||||
- V10.352 補市場情報 candidate queue review AI summary Telegram dispatch report catalog record commit gate:新增 read-only report catalog record commit builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 run receipt 後審核外部 CLI catalog record DB commit、post-write smoke、操作員 commit gate 確認與後續 closeout separate gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 review_state、不掛 scheduler。
|
||||
- V10.351 補市場情報 candidate queue review AI summary Telegram dispatch report catalog record run receipt:新增 read-only report catalog record run receipt builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 run readiness 後審核外部 CLI writer output、catalog record key/hash、DB commit receipt 與 post-write smoke;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 review_state、不掛 scheduler。
|
||||
- V10.348 補市場情報 candidate queue review AI summary Telegram dispatch report catalog record run readiness:新增 read-only report catalog record run readiness builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 run package 後檢查 payload manifest、manual CLI command、backup/dry-run、run receipt 與 postwrite smoke 條件;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不更新 review_state、不掛 scheduler。
|
||||
- V10.347 補市場情報 candidate queue review AI summary Telegram dispatch report catalog record run package:新增 read-only report catalog record run package builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 record write gate 後整理 payload manifest、CLI command bundle、backup/dry-run trace 與後續 run readiness separate gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不更新 review_state、不掛 scheduler。
|
||||
- V10.344 補市場情報 candidate queue review AI summary Telegram dispatch report catalog record write gate:新增 read-only report catalog record write builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 write preflight 後檢查 catalog record key/schema/hash trace、operator dry-run、backup 與 commit separate gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不寫 catalog record、不更新 review_state、不掛 scheduler。
|
||||
- V10.342 補市場情報 candidate queue review AI summary Telegram dispatch report catalog write preflight:新增 read-only report catalog write preflight builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report catalog index 後整理 catalog record identity、write source trace、record schema preflight 與 runtime safety,只放行到後續 report catalog record write gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫 catalog preflight file、不寫 catalog record、不更新 review_state、不掛 scheduler。
|
||||
- V10.339 補市場情報 candidate queue review AI summary Telegram dispatch report catalog index:新增 read-only report catalog index builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report catalog handoff 後整理 catalog index identity、handoff source trace、index manifest 與 runtime safety,只放行到後續 report catalog write preflight gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫 catalog index file、不寫 catalog record、不更新 review_state、不掛 scheduler。
|
||||
- V10.338 補市場情報 candidate queue review AI summary Telegram dispatch report catalog handoff:新增 read-only report catalog handoff builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report archive summary 後整理 catalog identity、artifact manifest、section keys 與 hash traceability,只放行到後續 report catalog index gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫 catalog record、不更新 review_state、不掛 scheduler。
|
||||
- V10.335 補市場情報 candidate queue review AI summary Telegram dispatch report archive summary:新增 read-only report archive summary builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report archive 後整理 report identity、archive traceability、integrity review 與 runtime safety sections,只放行到後續 report catalog handoff gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不更新 review_state、不掛 scheduler。
|
||||
- V10.334 強化 MOMO/PChome 核心比價第二波:matcher 補常見品牌 alias、任選/平輸/國別 noise 收斂、刀把/刀片/刀頭等件數解析與系列衝突硬否決,避免 Gillette/Schick 同品牌不同系列或刀片數被誤當同款;新增近門檻候選重新評分流程,會把舊 low_score 中 0.70 以上且非 hard veto、有 PChome product_id 的候選先批次重評,再補抓高價未配對商品;商品看板新增 PChome 補抓產線狀態卡,顯示 run id 階段、成功/低信心/無結果/挑品寫入與錯誤,不再讓核心比價補抓變成黑盒。
|
||||
- V10.333 補市場情報 candidate queue review AI summary Telegram dispatch report archive:新增 read-only report archive builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report closeout 後審核 archive/closeout/receipt/report output artifact path、hash/章節、archive manifest、retention policy 與後續 archive summary separate gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不更新 review_state、不掛 scheduler。
|
||||
- V10.332 補市場情報 candidate queue review AI summary Telegram dispatch report closeout:新增 read-only report closeout builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report run receipt 後審核 closeout artifact、receipt/report artifact path、hash/章節覆核、後續 report archive separate gate 與 runtime boundary;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不更新 review_state、不掛 scheduler。
|
||||
- V10.331 強化 PChome 比價 matcher 邊界:正式端 pilot 先刷新 30 筆過期 identity_v2、補抓 15 筆高價未配對 SKU,確認 structured diagnostics 開始寫入;針對「同品牌、同核心多組件、無任何否決理由、價格正常」但因規格文字不完整卡在 0.74x 的候選,新增 strong_component_line_match 窄門補分,避免 Gennies 類完整套組被誤留在低信心,同時維持雙入組對單品、容量衝突、不同品牌與補充瓶硬否決。
|
||||
- V10.330 補市場情報 candidate queue review AI summary Telegram dispatch report run receipt:新增 read-only report run receipt builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report run readiness 後審核人工/獨立 job 產出的 report receipt、artifact path/hash、必要章節、summary hash 與 runtime boundary;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不更新 review_state、不掛 scheduler。
|
||||
- V10.329 優化 `/growth_analysis` 冷快取策略:source fingerprint 未變時,成長分析共享快取由 30 分鐘延長為 6 小時有效;每日匯入仍會主動清除快取,避免資料更新後沿用舊圖表,同時降低正式端重啟或冷 worker 重新掃 `realtime_sales_monthly` 的 14 秒級等待。
|
||||
- V10.328 強化 MOMO/PChome 核心比價準確性第一波:補高頻品牌 alias、中文商品線 bigram 訊號、保健/包裝同義單位與買送件數解析,搜尋詞改為品牌/核心/主規格三層;PChome 比對嘗試與正式快照補存 URL、圖片、庫存與結構化 diagnostics,商品列表用 tone 分流顯示尚未搜尋、低信心、身份否決、單位價與過期狀態,不再把不同問題全部壓成灰色待比對;同步持久化首頁 / PChome coverage 熱路徑索引,避免重開機後慢查詢回歸。
|
||||
- V10.327 補 OpenClaw fallback 可觀測性:週報、月報、Meta、日報洞察、每日報告的 Gemini/NIM 備援 caller 納入 caller registry、AI 觀測台 agent group 與 Telegram 狀態統計,並補 MCP collector Ollama-first regression test,避免 fallback 真實使用量在觀測層被歸類成未知或漏算。
|
||||
- V10.326 補市場情報 candidate queue review AI summary Telegram dispatch report run readiness:新增 read-only report run readiness builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report run package 後整理 report generation readiness manifest、manual report command boundary、artifact path gate 與後續 report run receipt gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不派送 Telegram、不開 DB、不寫檔、不產報表、不更新 review_state、不掛 scheduler。
|
||||
- V10.325 收斂 Gemini 主路徑:OpenClaw 週/月/meta/日報洞察、Telegram PPT 分析與 MCP fallback 全部改成先走 OllamaService 的 GCP-A → GCP-B → 111 三主機級聯;Gemini 只在 Ollama/NIM 不可用後作備援。Elephant Alpha resource_optimization 告警補上待處理 action_plans 焦點列表,避免只報隊列數字卻沒有可執行對象。
|
||||
- V10.324 補市場情報 candidate queue review AI summary Telegram dispatch report run package:新增 read-only report run package builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report input 後整理 run package contract、evidence refs、package sections 與後續 report run readiness gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不派送 Telegram、不開 DB、不寫檔、不產報表、不更新 review_state、不掛 scheduler。
|
||||
- V10.323 收斂市場情報 seed writer token hardening drift:正式端 service smoke 已確認 `seed_writer_cli_status` 不再回吐 `approval_token_hint`、不洩漏環境 token;輸出補 `api_reads_approval_token=false`、`api_executes_cli=false`、`api_writes_database=false`,讓 API/CLI 邊界可被 regression test 與 smoke 明確驗證。
|
||||
- V10.322 修正 Telegram 決策/審核推播舊入口:`price_decision_notify()` 改用 `send_telegram_with_result()` 統一套用 HTML sanitizer 與多 chat 結果彙整,並補齊 `price_decision(report_url=...)` 相容;RAG awaiting review 推播改用正確 `chat_ids=[...]` 呼叫,避免 Stage 4 人工審核按鈕因參數名錯誤完全送不出去。
|
||||
- V10.321 修正 Telegram HTML 發送格式:所有 `sendMessage` / `sendPhoto` caption 在 HTML parse mode 送出前會把 `<br>` / `<br/>` / `<BR />` 統一轉成換行,避免 Telegram Bot API 回 `Unsupported start tag "br"` 造成告警或報告送出失敗。
|
||||
- V10.320 補市場情報 candidate queue review AI summary Telegram dispatch report input:新增 read-only report input builder、獨立 report route extension、UI 按鈕與 deployment readiness smoke target,在 archive summary 後整理 report input sections、report contract、message evidence 與 dispatch audit traceability;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不派送 Telegram、不開 DB、不寫檔、不產報表、不更新 review_state、不掛 scheduler。
|
||||
- V10.319 補市場情報 candidate queue review AI summary Telegram dispatch archive summary:新增 read-only archive summary builder、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 Telegram dispatch archive 後整理 message identity、dispatch audit、artifact manifest 與後續 report input sections;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不派送 Telegram、不開 DB、不寫檔、不更新 review_state、不掛 scheduler。
|
||||
- V10.318 收緊 Elephant Alpha HITL 告警治理:`ea_escalation` 只有真正含 SKU/價格比較的 actions 才排成 TOP 待審 SKU 卡片;非 SKU 診斷改為「待確認事項」,並用測試鎖住價格類低信心但無 DB/Hermes 實證時只 suppress、不寫 human_review、不發 Telegram,避免空泛告警打擾人工審核。
|
||||
- V10.317 修正 PChome 比價覆蓋率分子:`fetch_competitor_coverage()` 的 valid_matches 改成 `ACTIVE + 有 MOMO 最新價` 商品與有效 PChome `identity_v2` 價格的交集,不再把非活躍或無 MOMO 現價的舊 competitor_prices 列入覆蓋率,避免 daily/growth/PPT/AI 報表高估比價資料品質。
|
||||
- V10.315 修正競品簡報/報表指定日期取價:`fetch_competitor_comparison_results()` 在有 start/end date 時改讀 `competitor_price_history` 的期間快照,MOMO 價格也取期間結束前最新價;沒有指定日期才使用目前有效 `competitor_prices`,避免把今天的 PChome 快取價塞回歷史 daily/growth/PPT 判讀。
|
||||
- V10.314 擴大 PChome 候選池與搜尋韌性:PChome 搜尋 API 改為依 limit 掃多頁並對 429/5xx/timeout 做有限重試;feeder 預設每個商品最多 5 組搜尋詞、每詞 20 候選、2 頁,且搜尋清理不再刪掉括號/方括號內的品牌與規格,讓正確候選更有機會進 matcher,而不是長期停在「待對比」。
|
||||
- V10.312 強化 PChome 商品身份比對防錯配:matcher 開始解析 mg/mcg 劑量、件組套組與多規格集合,60ml+150ml vs 60ml+20ml、10mg vs 20mg、10片 vs 10盒、精華 vs 化妝水都會進硬否決或單位價覆核,不再靠單一規格重疊放行;覆核診斷同步新增「劑量差異」標籤,降低核心比價錯配污染 daily/growth/PPT/AI 分析。
|
||||
- V10.311 統一競品價差語意:`fetch_competitor_comparison_results()`、competitor PPT 與 OpenClaw competitor prompt 全部改用 `MOMO - PChome`,正值代表 MOMO 較貴 / PChome 低價壓力,負值代表 MOMO 價格優勢;避免 daily/growth 顯示價格壓力但 PPT/AI 反向解讀。
|
||||
- V10.310 強化 MOMO/PChome 核心比價閉環:PChome feeder 搜尋候選只有強同款 `0.90` 才提前停止,避免第一個 0.76 次佳候選卡掉後續精準搜尋詞;人工否決的候選會被跳過並改挑下一個候選,不再讓已否決商品長期阻塞同 SKU。人工 `reject_identity`、`unit_price_required`、`needs_research` 會立即讓同候選正式 `competitor_prices` 過期,Dashboard 即使尚有舊價也不再顯示正式總價差;手機版比價覆核欄位標籤、覆核按鈕冒泡與候選證據顯示同步修正。
|
||||
- V10.308 修正商品列表 PChome 比價閉環狀態:`manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 不再掉回籠統「待比對」,改顯示「人工已否決 / 人工標記單位價 / 人工要求補搜尋」與後續 feeder 行為說明,避免人工覆核後 UI 看起來像沒有處理。
|
||||
- V10.307 將 PChome 人工覆核成效接進 daily/growth/PPT 共用資料出口:`fetch_competitor_coverage()` 讀取 `competitor_match_reviews` 最新決策,輸出人工採用、人工否決、人工單位價與採用率;`daily_sales` 與 `growth_analysis` 的比價資料品質區塊直接顯示這些閉環指標,讓報表與簡報不只看待審數,也能看人工處理成效。
|
||||
- V10.305 將 PChome 人工覆核回饋接回 feeder:下一輪搜尋若命中已被 `reject_identity` 否決的同一候選,會記錄 `manual_rejected` 並跳過正式寫入;已被標記 `unit_price_required` 的候選只保留單位價比較,不寫入正式總價差;人工 `accept_identity` 可保守覆蓋低分門檻但會打 `manual_review/manual_accept` 標籤,讓核心比價閉環可被後續報表與簡報追蹤。
|
||||
- V10.304 補 PChome 比價人工覆核決策閉環:新增 `competitor_match_reviews`、`/api/pchome-review/<sku>/decision` 與商品看板覆核列「採用同款 / 否決候選 / 標記單位價」動作;只有人工採用同款才寫入 `competitor_prices` + `competitor_price_history`,否決與單位價標記只追加 manual attempt 並關閉本輪覆核,避免錯配污染核心價差。
|
||||
- V10.302 補 PChome 比價覆核匯出與診斷原因:`filter=pchome_review` 每筆覆核把 matcher `reasons=` 翻成品牌不符、商品線不符、容量差異、組合差異、需單位價、價差極端等可行動標籤;新增 `/api/export/excel/pchome-review` 匯出完整覆核隊列、人工處置、候選 PChome、單位價比較與原始診斷,避免核心比價只停在籠統「待對比」。
|
||||
- V10.301 補市場情報 candidate queue review AI summary Telegram dispatch gate:新增 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate` 與 UI 按鈕,在 summary persistence closeout 後檢查 Telegram 訊息契約、channel label、artifact path、token 外洩風險與後續 run package promotion;API/UI 仍不讀 approval/Telegram token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不掛 scheduler。
|
||||
- V10.300 補商品看板比價覆核狀態分流:`filter=pchome_review` 新增全部、需單位價、身份否決、低信心、價格過期、找不到同款 segmented 篩選與分頁保留參數,讓 6,000+ 筆覆核隊列能依 matcher 診斷類型分批處理;同步修正覆核列表表頭/分頁連結狀態保留。
|
||||
- V10.299 補市場情報 candidate queue review AI summary persistence run closeout:新增 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_closeout` 與 UI 按鈕,在 receipt 通過後收尾 metadata_json persistence gate,確認 closeout artifact、操作員確認與後續 Telegram dispatch 必須另開 gate;API/UI 仍不讀 approval token、不執行 CLI、不連 DB、不寫 `metadata_json`、不派送 Telegram、不掛 scheduler。
|
||||
- V10.298 補市場情報 candidate queue review AI summary persistence run receipt:新增 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_receipt` 與 UI 按鈕,審核操作員貼回的 metadata_json CLI writer output、post-write smoke、dedupe key、summary payload hash、artifact path 與 token 外洩風險;API/UI 仍不讀 approval token、不執行 CLI、不連 DB、不寫 `metadata_json`、不派送 Telegram、不掛 scheduler。
|
||||
- V10.297 將 PChome 單位價覆核隊列接回商品看板第一屏:KPI 顯示待處理/需單位價覆核數,焦點區列出候選 PChome 商品、候選價、match score 與人工動作;新增 `filter=pchome_review` 的比價覆核隊列,讓使用者可直接進入待處理 SKU,不再只在 daily/growth/PPT 摘要看到統計。
|
||||
- V10.296 補核心 MOMO/PChome 比價第三層語意與覆核閉環:同核心商品但買送、套組、件數不同且只有單一基礎規格時標記 `unit_comparable`,只寫入 `competitor_match_attempts`;商品看板、daily/growth 報表、OpenClaw/PPT 摘要共用 `competitor_intel_repository` 的覆核隊列,顯示「需單位價比較」、候選商品、候選 PChome 價格與單位價換算證據;多容量/多品項套組仍保持不可比較,避免把不同販售組合直接寫進正式總價差。
|
||||
- V10.289 重排 Elephant Alpha L3 HITL `ea_escalation` Telegram 告警:改成專業 incident brief 格式,分成決策狀態、背景摘要、風險摘要、TOP 待審 SKU 與建議處置;價格行動會拆出 MOMO/PChome 價格、價差、人工處置與 PChome ID,避免長 bullet 難讀。
|
||||
- V10.284 關閉 Code Review Hermes LLM scan 預設路徑:Step 2 改 deterministic fast static scan,不再讓部署後先卡三段 Ollama timeout;若需要 LLM scan 可用 `CODE_REVIEW_HERMES_LLM_SCAN_ENABLED=true` 顯式開啟,仍只走本地矩陣、不走 Gemini。
|
||||
- V10.283 將 Code Review Hermes scan 收斂為 fast compact prompt:預設 2 檔 × 900 字、輸出 384 tokens,仍走 GCP-A → GCP-B → 111 本地矩陣,避免部署後 code_review_hermes 先卡三段 timeout。
|
||||
- V10.282 補齊 Code Review Hermes scan 本地模型矩陣:掃描階段也走 GCP-A `qwen2.5-coder:7b` → GCP-B `gemma3:4b` → 111 `hermes3:latest`,避免 `hermes3` 在三主機各卡 35s 後只留下 error;Hermes scan 不會啟用 Gemini。
|
||||
- V10.281 強化 Code Review OpenClaw 本地備援矩陣:主機順序仍為 GCP-A → GCP-B → 111,但改成 GCP-A `qwen2.5-coder:7b`、GCP-B `gemma3:4b`、111 `hermes3:latest`,三段本地 Ollama 全失敗後才允許 Claude/Gemini 備援。
|
||||
- V10.279 收斂 Code Review Ollama-first 路徑:OpenClaw assessment 預設改 `qwen2.5-coder:7b` + 45s/host timeout,Hermes scan 改 compact snippet + 35s/host timeout,避免三主機各卡 120s 後被迫觸發 Gemini 備援。
|
||||
- V10.278 補 PChome 競價摘要 30 分鐘共享快取與 feeder/backfill 主動清除,並新增市場情報 `candidate_queue_review_ai_summary_preflight` 預覽 gate;API 只檢查未來摘要輸入與 Ollama-first/Gemini-backup-only policy,不呼叫 LLM、不派 Telegram、不寫 DB、不掛 scheduler。
|
||||
- V10.276 修正 ElephantAlpha 價格類 Hermes prefetch timeout:`price_drop` / `market_opportunity` trigger 直接把 SQL 命中的 MOMO / PChome 價差實證轉成 HITL action lines,完整 Hermes LLM prefetch 預設關閉;無 DB 實證仍只記 suppressed telemetry / cooldown,不寫 `human_review`、不發空 Telegram。
|
||||
- V10.266 強化核心 MOMO/PChome 比價鏈路:新增 `marketplace_product_matcher.py` 身份比對、只讓 `identity_v2` 且分數 ≥ 0.76 的高信心配對進 Dashboard/AI/Excel/Daily/Growth/PPT,並建立 `competitor_intel_repository.py` 統一圖表與簡報資料出口;同品牌但不同型號、不同組數、套組/單品或多品項不一致會進待審,不進正式比價。
|
||||
- V10.267 專業化 ElephantAlpha `resource_optimization` 告警:不再讓 LLM 生成「48 小時預期效益 / 已執行」敘事,改由程式量測 action queue、P1/P2、pending_review、逾時項目與 CPU load;單純 backlog 不發 Telegram,只有可行動資源壓力才寫 `ai_insights(resource_pressure)` 並發送量測型告警。
|
||||
- V10.254 續補 `/growth_analysis` 快取命中效能:PostgreSQL source fingerprint 加 60 秒短 TTL,匯入 realtime_sales_monthly 後同步清除 growth shared cache 與短快取,避免快取命中仍頻繁掃大表 COUNT。
|
||||
- V10.253 修正 Elephant Alpha L3 HITL 空告警:價格類與資源調配低信心事件若沒有 Hermes/實證資料,只記 suppressed telemetry 與 cooldown,不寫 pending human_review、不發 Telegram;`resource_optimization` 會保留 queue/load 原始指標供追查。
|
||||
- V10.251 修正 OpenClaw Q&A 備援遙測:Ollama 主路徑仍為 GCP-A → GCP-B → 111,Gemini 只記為 `openclaw_qa_gemini_fallback`,NIM 只記為 `openclaw_qa_nim`;AI Calls 會把 legacy `openclaw_qa + gemini` 標成 Gemini 備援,避免再次誤判 Gemini-first。
|
||||
- V10.251 穩定 `/growth_analysis` 正式站速度:成長分析快取從單 worker memory 擴充為 `data/growth_analysis_cache.pkl` 跨 worker 共享快取,避免 Gunicorn 冷 worker 偶發掃明細表造成 5 秒級 TTFB;補 `tests/test_cache_manager.py` 覆蓋 shared file roundtrip 與清除行為。
|
||||
- V10.249 收斂 `/observability/ppt_audit_history` 手機與平板第一屏密度:將 4 個產線訊號從 hero 內移出成獨立狀態列,手機版維持 2 欄狀態卡並降低 hero 卡片間距;本機 10 個 AI 觀測台頁面 rendered visual contract 全數通過,PPT 頁 hero 高度 desktop/tablet/mobile 為 214/361/398px。
|
||||
- V10.246 強化 `/observability/ppt_audit_history` 視覺 QA runtime 可讀性:功能開關、轉檔器與視覺模型改成中文 checklist,Vision QA 狀態卡直接顯示 runtime 就緒資訊,DB 產出狀態統一為「已產出」。
|
||||
- V10.245 重整 `/observability/ppt_audit_history` 首屏資訊階層:改成簡報操作摘要、最新可預覽簡報、下一步動作與自適應報表類型 segmented grid;產線覆蓋矩陣改為下方驗收明細,避免一進頁只看到大量「產線狀態」或類型按鈕右側溢出。
|
||||
- V10.242 修正 `/metabase`、`/grist` 外部工具入口:全域導覽固定回 momo-pro 內部橋接頁,避免資料協作錯連其他專案站;入口頁補路由狀態、設定診斷與可用替代分析入口,降低空白頁誤判。
|
||||
- V10.221 補 `/observability/ppt_audit_history` AiderHeal 背景任務可見性:正在修復中的簡報會顯示於產線頁,並提供 JSON 狀態端點讓派工後即時刷新,避免重新整理後不知道是否已在修。
|
||||
- V10.218 補 `/observability/ppt_audit_history` AiderHeal 去重鎖:同一份簡報已在背景修復時,再次點擊會回「已在執行中」,避免重複開 SSH / 模型 / git 修復流程。
|
||||
- V10.217 讓 `/observability/ppt_audit_history` 的 AiderHeal 派工改為非阻塞背景任務:頁面立即回「已排入」,修復工作在背景執行,避免瀏覽器與 Gunicorn worker 等 SSH、模型與 git push 到超時。
|
||||
- V10.216 修正 `/observability/ppt_audit_history` 的 AiderHeal 派工斷點:失敗簡報即使只有 `issues_found` 診斷摘要也能一鍵送修,並修正 `execute_code_fix` 參數與 dict 回傳解析,避免按鈕 400/500。
|
||||
- V10.215 強化 `/observability/ppt_audit_history` 視覺問題追蹤:將 `issues_found` 拆成投影片、問題類型、問題文字與回放入口,新增「視覺問題追蹤」面板,讓問題簡報能直接定位與預覽。
|
||||
- V10.213 補 `/observability/ppt_audit_history` 視覺 QA 診斷摘要:審核歷史與 Action Queue 直接顯示 `ppt_audit_results.issues_found` 的投影片問題,讓「有問題」可追查,不再只剩問題數或空白錯誤欄。
|
||||
- V10.212 修正 PPT 視覺 QA 的 Ollama 三主機 fallback:當 Primary/Secondary request timeout 超過 unhealthy TTL 時,第三輪仍強制打 111 final fallback;PPT 截圖送模型前轉輕量 JPEG、縮小輸出 token,降低單檔審核耗時。
|
||||
- V10.211 補 `/observability/ppt_audit_history` 全類型視覺 QA:審核歷史不再限 daily,頁面新增「立即視覺 QA」非阻塞補跑,結果寫入 `ppt_audit_results`;模型失敗時也保留 slide error,避免產線狀態只剩空白。
|
||||
- V10.210 補 `/observability/ppt_audit_history` 審核歷史同頁回放:每筆 daily 視覺審核紀錄的動作欄新增「回放」按鈕,沿用 PDF 預覽抽屜並保留下載/開新頁,讓問題追查不必再回檔案表找簡報。
|
||||
- V10.208 修正 `/observability/ppt_audit_history` 同頁預覽抽屜 selector:Modal 標題改用獨立 `data-ppt-preview-modal-title`,避免與多個預覽連結的資料屬性衝突。
|
||||
- V10.207 強化 `/observability/ppt_audit_history` 同頁線上預覽:所有可預覽簡報按鈕改為開啟頁內 PDF 預覽抽屜,保留開新頁與下載,降低產線頁來回跳轉成本並改善手機操作。
|
||||
- V10.205 補 `/observability/ppt_audit_history` 本頁批次 PDF 預熱:Preview Workbench 可一鍵預熱頁面上尚未快取的 PPTX,沿用單檔 JSON 端點逐一建立 PDF 快取並即時更新狀態。
|
||||
- V10.203 補 `/observability/ppt_audit_history` 單檔 PDF 預熱操作:未快取的可預覽 PPTX 會顯示「預熱 PDF」,透過 JSON 端點建立 PDF 快取並即時更新頁面狀態。
|
||||
- V10.201 強化 `/observability/ppt_audit_history` 線上預覽可診斷性:產線清單不觸發轉檔即可顯示 PDF 預覽快取狀態,Pipeline Health、Preview Workbench 與已產檔案表同步標記「PDF 快取 / 首次轉檔」。
|
||||
- V10.199 讓 `/observability/ppt_audit_history` Action Queue 可直接處理異常:待補齊與異常優先項目新增單一報表「重跑」按鈕,透過既有非阻塞背景產線排入指定 report_type。
|
||||
- V10.197 強化 `/observability/ppt_audit_history` Action Queue:新增「異常優先」lane,將產出失敗、PPTX 檔案異常、視覺 QA 失敗拉到最前面,並顯示錯誤訊息與可預覽入口。
|
||||
- V10.196 補 `/observability/ppt_audit_history` Action Queue:把待補齊、可預覽、視覺 QA、DB 寫入集中成工作隊列,讓使用者不用在多張卡與表格間找下一個處理點。
|
||||
- V10.194 重整 `/observability/ppt_audit_history` 產線資訊階層:新增 Pipeline Health、五段式流程階段、排程/覆蓋/DB/預覽/視覺 QA 狀態摘要,讓「已產生」改為可判斷的目標產生、其他版本、待排程補齊等狀態。
|
||||
- V10.192 補 `/observability/ppt_audit_history` 最近可預覽簡報 workbench:最新 4 份 PPT 直接在控制台下方提供線上預覽與下載,降低使用者找檔案的操作成本;完整檔案清單仍保留在下方表格。
|
||||
- V10.190 補 `/observability/ppt_audit_file/<filename>` 站內線上預覽:PPTX 由 LibreOffice 轉 PDF 快取後以 iframe 預覽,保留原始 PPTX 下載;Dockerfile 加 `libreoffice-impress`,compose 預設啟用 `PPT_VISION_ENABLED=true`,PPT 產線頁新增視覺 QA 停用原因與更精簡的控制台式排版。
|
||||
- V10.188 補強 `/observability/ppt_audit_history` PPT 視覺 QA 產線:頁面明確呈現每日、每週、每月、每季、每半年、每年定期產出節奏,並顯示 `ppt_generation_runs` DB 寫入紀錄;保留自動補齊缺漏與資料庫/檔案覆蓋狀態。
|
||||
- V10.187 修正 `/daily_sales`、`/growth_analysis` 圖表空白:Chart JSON 改從 `<template>.content.textContent` 讀取,補空資料診斷狀態;成長分析改用 realtime 明細新鮮度覆蓋過期月結摘要,並為 growth cache 加入資料指紋。
|
||||
- V10.151 接續前端 V3 全站 UI/UX:廠商缺貨 `/vendor-stockout/vendor-management`、`/vendor-stockout/send-email`、`/vendor-stockout/history` 改走新版 `ewoooc_base.html` shell 與 `page-vendor-tools.css`,移除舊紫藍 navbar/live route。
|
||||
- V10.151 補 `/abc_analysis/detail` 新版 ABC 詳情頁與安全 loading state,移除 raw HTML fallback,資料表維持正式快取資料來源與匯出連結。
|
||||
- V10.151 補 `/login` 新版登入頁:暖紙背景、點陣視覺、EwoooC 品牌、手機版 390px 無水平 overflow。
|
||||
@@ -77,18 +315,71 @@
|
||||
- Phase 26 platform seed CLI writer:`scripts/market_intel_seed_writer.py` 支援 CLI-only 受控寫入,必須同時帶 `--execute`、`--apply-real-write` 與確認 token 才會以 SQLAlchemy Core 短 transaction upsert `market_platforms`;API 仍不執行寫入,不建立 ORM session、不連外、不掛 scheduler;V10.101 補強 insert 顯式寫入 `created_at` / `updated_at`,避免正式 schema 無 default 時觸發 not-null rollback。
|
||||
- 正式環境 V10.101 已白名單部署:備份 `/tmp/codex_market_intel_v10100_predeploy_20260513_103809.tgz`、`/tmp/codex_market_intel_v10101_predeploy_20260513_104053.tgz` 與 seed 前快照 `/tmp/codex_market_platforms_v10100_before_20260513_103809.json`;僅 recreate `momo-app`,未碰 `momo-db`、未使用 `--remove-orphans`。CLI seed 首次因 timestamp not-null rollback,V10.101 修正後成功 insert `momo/pchome/coupang/shopee` 四筆 seed;read-only diff 顯示 existing=4、missing=0、matching=4,正式頁 console error 0。
|
||||
- 2026-05-13 Codex 只讀複核:`https://mo.wooo.work/health` 回報 V10.111;`/api/market_intel/status` 仍為 `enabled=false`、`crawler_enabled=false`、`write_enabled=false`、`dry_run_only=true`、phase 26;`/api/market_intel/platform_seed_db_diff?execute=true&platform=all` 只讀確認 `market_platforms` 已有 `momo/pchome/coupang/shopee` 四筆,`existing_seed_count=4`、`missing_codes=[]`、`database_write_executed=false`。
|
||||
- 注意:正式端 `/api/market_intel/seed_writer_cli_status?execute=true&platform=all` 仍回傳舊版 `approval_token_hint=APPROVED_MARKET_INTEL_SEED_WRITE_V1` 與固定 token gate 文案,與 main 已入庫的一次性環境 token hardening 不一致;下次正式白名單部署需優先同步 `services/market_intel/seed_writer_cli.py`、`services/market_intel/service.py`、`scripts/market_intel_seed_writer.py`,並 smoke 確認不再回吐 `approval_token_hint`。
|
||||
- Schema smoke:`tests/test_market_intel_skeleton.py` 檢查 `Base.metadata` 內含 ADR-035 七張 `market_*` tables。
|
||||
- V10.323 已補正式端 seed writer token-hardening smoke:`seed_writer_cli_status` 不回吐 `approval_token_hint`、不洩漏環境 token,且明確標示 API 不讀 approval token、不執行 CLI、不寫 DB。
|
||||
- Phase 27 legacy source bridge preview:新增 `services/market_intel/legacy_source_bridge.py` 與 `/api/market_intel/legacy_source_bridge`,只讀盤點既有 `promo_products`、`competitor_prices`、`competitor_price_history`,產生導入 `market_*` 的 mapping / dedupe / blocked operation preview;預設 `execute=false` 不連 DB,`execute=true` 也只做 read-only query,不寫 DB、不建立 ORM session、不連外、不掛 scheduler;UI 新增「既有資料橋接預覽」panel;版本同步至 V10.182。
|
||||
- Phase 45 migration live smoke preview:新增 `/api/market_intel/migration_live_smoke` 與 UI「正式 DB 只讀 smoke」panel;預設 `execute=false` 只回 planned,人工 smoke 才可用 `execute=true` 整理 catalog / seed diff 結果,不執行 migration、不寫 DB、不跑 rollback、不掛 scheduler;版本同步至 V10.207。
|
||||
- Phase 46 live DB inventory preview:新增 `/api/market_intel/live_db_inventory` 與 UI「正式 DB 庫存總覽」panel;預設 `execute=false` 不連 DB,人工 smoke 才可用 `execute=true` 對 `market_*` tables 做只讀 count / group by,建立平台、活動、商品、比對、告警與 crawler run 基準;版本同步至 V10.209。
|
||||
- Phase 47 manual sample fetch plan:新增 `/api/market_intel/manual_sample_plan` 與 UI「人工樣本 Fetch 計畫」panel;預設只輸出平台順序、每平台 1 個公開入口、MCP gate、操作員步驟與備援,不抓外站、不寫 DB、不建立 crawler run、不掛 scheduler;版本同步至 V10.214。
|
||||
- Phase 48 manual sample acceptance contract:新增 `/api/market_intel/manual_sample_acceptance` 與 UI「樣本結果驗收契約」panel;定義 sample result 必要欄位、diagnostics 欄位、驗收門檻、拒收條件、人工決策與升級順序,不載入 sample result、不抓外站、不允許候選導入、不寫 DB;版本同步至 V10.215。
|
||||
- Phase 49 manual sample result review:新增 `services/market_intel/manual_sample_review.py`、`/api/market_intel/manual_sample_review` 與 UI「樣本結果審核預覽」panel;以純函式評估人工 sample result 是否可進候選預覽,預設不載入結果、不抓外站、不存檔、不寫 DB、不允許候選導入、不掛 scheduler;版本同步至 V10.216。
|
||||
- Phase 50 manual sample review evaluate:新增 `/api/market_intel/manual_sample_review/evaluate` POST 與 UI JSON 審核入口,允許操作員貼入單筆 sample result 即時回傳 PASS/BLOCK;不保存 payload、不回吐完整 HTML、不寫 DB、不建立候選活動、不允許候選導入、不掛 scheduler;版本同步至 V10.219。
|
||||
- V10.220 補 Phase 50 UI POST CSRF header:`manual_sample_review/evaluate` 保持 CSRF 保護,頁面 fetch 送出 `X-CSRFToken`,不豁免安全檢查。
|
||||
- Phase 51 manual sample candidate handoff:新增 `/api/market_intel/manual_sample_review/candidate_handoff` POST 與 UI handoff 按鈕,將已通過審核的 sample result 轉成只讀候選活動 preview payload;不保存 handoff、不建立 review queue、不寫 market_*、不允許候選導入、不掛 scheduler;版本同步至 V10.222。
|
||||
- Phase 52 manual sample candidate queue draft:新增 `services/market_intel/manual_sample_candidate_queue.py`、`/api/market_intel/manual_sample_review/candidate_queue_draft` POST 與 UI queue 草案按鈕,將 handoff 候選轉成只讀人工審核 queue draft;不建立正式 queue、不保存草案、不寫 market_*、不自動核准候選、不掛 scheduler;版本同步至 V10.223。
|
||||
- V10.224 補 PPT 報表覆蓋矩陣:`/observability/ppt_audit_history` 將每個定義簡報同列串起 DB 寫入、線上預覽、視覺 QA 與交付狀態,並提供預覽、預熱、重跑操作,避免只顯示「目標已產生」。
|
||||
- Phase 53 manual sample candidate queue approval:新增 `/api/market_intel/manual_sample_review/candidate_queue_approval` POST 與 UI 送審 gate 按鈕,將 queue draft row preview 對齊既有 `market_alert_review_queue` 契約,檢查必填欄位、寫入 flags、備份與人工批准 gate;不建立 approval record、不寫 review queue、不開 DB transaction、不掛 scheduler;版本同步至 V10.225。
|
||||
- V10.226 補 PPT 視覺 QA runtime checklist:`/observability/ppt_audit_history` 在視覺模型未就緒時顯示 Feature Flag、LibreOffice、Vision Model 三段檢查與下一步操作,避免只看到「停用」而不知道卡在哪。
|
||||
- Phase 54 manual sample candidate queue transaction:新增 `/api/market_intel/manual_sample_review/candidate_queue_transaction` POST 與 UI transaction preview 按鈕,將 queue row preview 轉成 `market_alert_review_queue` idempotent insert statement、payload hash 與 rollback plan;不開 DB connection、不開 transaction、不 commit、不建立 approval record;版本同步至 V10.227。
|
||||
- V10.228 補 PPT 視覺 QA 背景狀態卡:新增 `/observability/ppt_audit/vision_status` 與頁面 Vision QA 狀態卡,讓立即視覺 QA 排入後可看 queued/running/completed/error 與最近審核摘要,不必刷新猜測。
|
||||
- V10.229 修正 PPT 視覺 QA 多 worker 狀態漂移:將 queued/running/completed/error 寫入 `/app/data/ppt_vision_audit_status.json` runtime state,所有 Gunicorn worker 共用同一份狀態並阻擋重複排入。
|
||||
- Phase 55 candidate queue writer CLI gate:新增 `/api/market_intel/manual_sample_review/candidate_queue_writer_status` POST、`scripts/market_intel_candidate_queue_writer.py` 與 UI writer gate 按鈕,定義 `MARKET_INTEL_QUEUE_WRITE_APPROVAL` 一次性 token、execute/apply flags、備份、migration smoke 與 rollback gate;本階段仍不開 DB connection、不寫 `market_alert_review_queue`、不 commit、不掛 scheduler;版本同步至 V10.230。
|
||||
- Phase 56 candidate queue writer preflight:新增 `/api/market_intel/manual_sample_review/candidate_queue_writer_preflight` POST 與 `services/market_intel/candidate_queue_writer_preflight.py`,檢查 transaction payload key 到 `market_alert_review_queue` 欄位映射、缺欄與 dedupe unique index;頁面預設 execute=false 不連 DB,CLI 可明確 `--read-only-preflight` 只讀 catalog;版本同步至 V10.232。
|
||||
- Phase 57 candidate queue writer CLI transaction:`scripts/market_intel_candidate_queue_writer.py` 在 CLI-only 情境支援受控 transaction,必須同時通過 transaction payload、read-only preflight、`--execute`、`--apply-real-write`、一次性 token、備份確認與 migration live smoke 才會以 SQLAlchemy Core idempotent insert `market_alert_review_queue`;API/UI 仍不傳 token、不連 DB、不寫 queue、不掛 scheduler;版本同步至 V10.234。
|
||||
- V10.235 補 PPT 視覺 QA stale recovery:背景狀態寫入 worker PID;若部署 reload 後舊 PID 已不存在,`/observability/ppt_audit/vision_status` 會自動把 running 轉為可診斷 error 並允許重新排入,避免人工清 runtime state。
|
||||
- Phase 58 candidate queue writer post-write smoke:新增 `services/market_intel/candidate_queue_writer_postwrite_smoke.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_postwrite_smoke` 與 UI smoke 按鈕,依 transaction preview 的 dedupe key 只讀查詢 `market_alert_review_queue`,讓 CLI 真寫入後可驗證 row 是否存在;頁面預設 execute=false 不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.236。
|
||||
- Phase 59 candidate queue writer operator drill:新增 `services/market_intel/candidate_queue_writer_operator_drill.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_operator_drill` 與 UI drill 按鈕,組裝 reviewed sample、備份、read-only preflight、CLI writer、post-write smoke 的操作員順序;API/UI 不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.237。
|
||||
- V10.238 補業績圖表 runtime QA 與分析 tabs 窄版修正:新增 `quick_review --sales-charts` 檢查 `/daily_sales`、`/growth_analysis` 的 Chart.js ready、可繪製資料集與 canvas 非空白;同時把分析報表 tabs 手機版改為自適應 grid,避免 Metabase/Grist 外部連結超出右側。
|
||||
- Phase 60 candidate queue writer run package:新增 `services/market_intel/candidate_queue_writer_run_package.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_run_package` 與 UI run package 按鈕,整理正式 CLI 小流量寫入前的 payload manifest、required artifacts、command bundle、operator signoff 與 rollback plan;API/UI 不產檔、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.240。
|
||||
- Phase 61 candidate queue writer run readiness:新增 `services/market_intel/candidate_queue_writer_run_readiness.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_run_readiness` 與 UI readiness 按鈕,檢查 reviewed sample 檔案路徑、備份路徑、preflight 輸出、migration live smoke、shell-only token acknowledgement 與禁止 token 進 API;API/UI 不產檔、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler;版本同步至 V10.245。
|
||||
- Phase 62 candidate queue writer run receipt:新增 `services/market_intel/candidate_queue_writer_run_receipt.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_run_receipt` 與 UI receipt 按鈕,審核 CLI 寫入後的 writer output、post-write smoke、dedupe key 一致性與 artifact 路徑;API/UI 不回吐 receipt 原文、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不掛 scheduler;版本同步至 V10.247。
|
||||
- Phase 63 candidate queue writer run closeout:新增 `services/market_intel/candidate_queue_writer_run_closeout.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_writer_run_closeout` 與 UI closeout 按鈕,在 receipt 通過後檢查 closeout artifact、操作員人工 queue review/read-only inventory 確認與安全 promotion gate;API/UI 不回吐原始 receipt、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不掛 scheduler;版本同步至 V10.248。
|
||||
- Phase 64 candidate queue review handoff:新增 `services/market_intel/candidate_queue_review_handoff.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_handoff` 與 UI handoff 按鈕,將 writer closeout 轉成人工 queue review / read-only inventory 交接契約;API/UI 不讀 approval token、不查 DB、不更新 review_state、不補寫 queue、不掛 scheduler;版本同步至 V10.251。
|
||||
- Phase 65 candidate queue review inventory:新增 `services/market_intel/candidate_queue_review_inventory.py`、`routes/market_intel_review_routes.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_inventory` 與 UI inventory 按鈕,把 handoff、post-write smoke、live DB inventory 合併成只讀人工審核庫存檢查;預設不連 DB,人工只讀查詢仍不更新 review_state、不補寫 queue、不讀 token、不掛 scheduler;版本同步至 V10.252。
|
||||
- Phase 66 candidate queue review decision:新增 `services/market_intel/candidate_queue_review_decision.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision` 與 UI decision 按鈕,將通過 inventory 的 queue row 整理成人工決策草案;API/UI 不更新 review_state、不寫 decision record、不讀 token、不掛 scheduler;版本同步至 V10.254。
|
||||
- Phase 67 candidate queue review decision approval:新增 `services/market_intel/candidate_queue_review_decision_approval.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_approval` 與 UI approval gate 按鈕,檢查人工決策草案是否可進入下一個 CLI-only transaction preview;API/UI 不更新 review_state、不寫 decision record、不建立 approval record、不讀 token、不掛 scheduler;版本同步至 V10.255。
|
||||
- Phase 68 candidate queue review decision transaction:新增 `services/market_intel/candidate_queue_review_decision_transaction.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_transaction` 與 UI transaction preview 按鈕,將 approval update preview 轉成 `review_state` update statement、payload hash 與 rollback plan;API/UI 不更新 review_state、不開 DB connection、不執行 CLI、不讀 token、不掛 scheduler;版本同步至 V10.256。
|
||||
- Phase 69 candidate queue review decision writer CLI gate:新增 `services/market_intel/candidate_queue_review_decision_writer_cli.py`、`scripts/market_intel_review_decision_writer.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_status` 與 UI writer gate 按鈕,先建立 shell-only review_state writer gate 與 command bundle;writer implementation 本階段保持 disabled,API/UI 不讀 token、不執行 CLI、不連 DB、不更新 review_state、不掛 scheduler;版本同步至 V10.257。
|
||||
- Phase 70 candidate queue review decision writer preflight:新增 `services/market_intel/candidate_queue_review_decision_writer_preflight.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_preflight` 與 UI preflight 按鈕,檢查 writer status、review_state update payload、狀態轉換與禁止 token 進 API;API/UI 即使收到 execute/apply_real_write 也不連 DB、不執行 CLI、不更新 review_state、不 commit、不讀 token、不掛 scheduler;版本同步至 V10.258。
|
||||
- V10.259 補 Phase 70 preflight 合約與 OCLearn queue 時區:preflight 補 planned/read-only catalog probe 欄位、dedupe unique index 檢查與 route 重複註冊清理;OCLearn embedding queue 的 created_at/updated_at/stale cutoff 改為台北 naive,避免 UTC/台北時間差讓 processing 任務卡住。
|
||||
- Phase 71 candidate queue review decision writer post-write smoke:新增 `services/market_intel/candidate_queue_review_decision_writer_postwrite_smoke.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_postwrite_smoke` 與 UI smoke 按鈕,人工 CLI 更新 review_state 後可用 dedupe key 只讀驗證 row 是否存在且 state 符合預期;API/UI 預設不連 DB,execute=true 也只讀查詢,不更新 review_state、不 commit、不讀 token、不掛 scheduler;版本同步至 V10.260。
|
||||
- Phase 72 candidate queue review decision writer operator drill:新增 `services/market_intel/candidate_queue_review_decision_writer_operator_drill.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_operator_drill` 與 UI drill 按鈕,將 review_state CLI 更新前後的 transaction JSON、備份、preflight、CLI writer、post-write smoke 與 rollback plan 組成可稽核操作順序;API/UI 不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.261。
|
||||
- Phase 73 candidate queue review decision writer run package:新增 `services/market_intel/candidate_queue_review_decision_writer_run_package.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_package` 與 UI package 按鈕,將 review_state transaction、preflight、operator drill、writer gate、post-write smoke、必要 artifact 與 rollback plan 組成正式 CLI 更新前的可稽核 run package;API/UI 不寫檔、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.262。
|
||||
- Phase 74 candidate queue review decision writer run readiness:新增 `services/market_intel/candidate_queue_review_decision_writer_run_readiness.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_readiness` 與 UI readiness 按鈕,檢查 review_state CLI 更新前的 transaction JSON、備份、preflight、shell-only token 與 post-write smoke 計畫是否齊備;API/UI 不寫檔、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.264。
|
||||
- Phase 75 candidate queue review decision writer run receipt:新增 `services/market_intel/candidate_queue_review_decision_writer_run_receipt.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_receipt` 與 UI receipt 按鈕,審核 review_state CLI 更新後的 writer output、post-write smoke、dedupe key 一致性、artifact 路徑與 token 外洩風險;API/UI 不回吐 receipt 原文、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.266。
|
||||
- Phase 76 candidate queue review decision writer run closeout:新增 `services/market_intel/candidate_queue_review_decision_writer_run_closeout.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_closeout` 與 UI closeout 按鈕,在 review_state receipt 通過後整理 closeout gate、操作員 closeout artifact、post-closeout read-only inventory 確認與 promotion 摘要;API/UI 不回吐 receipt 原文、不讀 token、不執行 CLI、不連 DB、不更新 review_state、不 commit、不掛 scheduler;版本同步至 V10.268。
|
||||
- Phase 77 candidate queue review decision post-closeout inventory:新增 `services/market_intel/candidate_queue_review_decision_post_closeout_inventory.py`、`routes/market_intel_review_post_routes.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_decision_post_closeout_inventory` 與 UI inventory 按鈕,在 review_state closeout 後整理 post-write smoke、live inventory、dedupe key 與 review_state 結果;API/UI 不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.270。
|
||||
- Phase 78 candidate queue review completion archive:新增 `services/market_intel/candidate_queue_review_completion_archive.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_completion_archive` 與 UI archive 按鈕,在 post-closeout inventory 通過後整理 receipt、closeout、inventory、dedupe key、review_state row snapshot 與 artifact path manifest;API/UI 不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.273。
|
||||
- Phase 79 candidate queue review archive summary:新增 `services/market_intel/candidate_queue_review_archive_summary.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_archive_summary` 與 UI summary 按鈕,在 review completion archive 後整理可供摘要/報表審核的結構化輸入;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.276。
|
||||
- Phase 80 candidate queue review AI summary preflight:新增 `services/market_intel/candidate_queue_review_ai_summary_preflight.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_preflight` 與 UI preflight 按鈕,在 archive summary 後檢查 Ollama-first 三主機級聯與 Gemini 備援邊界;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.278。
|
||||
- Phase 81 candidate queue review AI summary run package:新增 `services/market_intel/candidate_queue_review_ai_summary_run_package.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_run_package` 與 UI package 按鈕,在 AI summary preflight 後整理手動 Ollama 摘要任務包、prompt contract 與輸出 schema;API/UI 不呼叫 LLM、不派送 Telegram、不寫 run package、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.280。
|
||||
- Phase 82 candidate queue review AI summary output receipt:新增 `services/market_intel/candidate_queue_review_ai_summary_output_receipt.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_output_receipt` 與 UI receipt 按鈕,在 run package 後驗收人工 Ollama 摘要輸出的 schema、evidence_refs 與 model_route;API/UI 不呼叫 LLM、不派送 Telegram、不寫 receipt、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.285。
|
||||
- Phase 83 candidate queue review AI summary persistence preflight:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_preflight.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_preflight` 與 UI preflight 按鈕,在 output receipt 後整理 future CLI-only `market_alert_review_queue.metadata_json.ai_summary_review` persistence contract、payload hash 與 metadata patch preview;API/UI 不呼叫 LLM、不派送 Telegram、不寫 preflight、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler;版本同步至 V10.286。
|
||||
- Phase 84 candidate queue review AI summary persistence transaction:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_transaction.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_transaction` 與 UI transaction 按鈕,在 persistence preflight 後產生 future CLI-only `metadata_json` UPDATE statement preview、parameter preview 與 rollback plan;API/UI 不開 DB、不執行 SQL、不寫 transaction、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.287。
|
||||
- Phase 85 candidate queue review AI summary persistence writer preflight:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_writer_preflight.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_writer_preflight` 與 UI writer preflight 按鈕,在 transaction preview 後檢查 CLI-only writer contract、metadata_json backup requirement、post-write smoke requirement 與 artifact path gate;API/UI 不開 DB、不執行 SQL、不寫 preflight、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.290。
|
||||
- Phase 86 candidate queue review AI summary persistence run package:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_run_package.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_package` 與 UI run package 按鈕,在 writer preflight 後整理 payload manifest、CLI command bundle、required artifacts、operator signoff 與 rollback plan;API/UI 不開 DB、不執行 SQL、不寫 run package、不寫 summary record、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.294。
|
||||
- Phase 87 candidate queue review AI summary persistence run readiness:新增 `services/market_intel/candidate_queue_review_ai_summary_persistence_run_readiness.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_readiness` 與 UI readiness 按鈕,在 run package 後檢查 artifact path、metadata_json backup、read-only preflight、shell-only token 與 post-write smoke 計畫;API/UI 不開 DB、不執行 SQL、不寫 readiness artifact、不寫 metadata_json、不讀 token、不執行 CLI、不更新 review_state、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;版本同步至 V10.297。
|
||||
- V10.248 補市場情報 390px preview panel QA:sample review 工具列改為 textarea + 可換行 action rail,移除舊的硬編 8 欄 grid;`check_responsive_overflow` 新增 `--screenshot-all`,本機 390x844 `/market_intel` 真頁面 QA 通過且 overflow=0。
|
||||
- V10.250 補 Code Review Gemini 備援遙測護欄:Ollama 主路徑失敗時 `fallback_to` 明確指向 `code_review_openclaw_gemini`,測試鎖住「Gemini 不得記成 `code_review_openclaw` 主 caller」;AI Calls 觀測台會把 legacy `code_review_openclaw + gemini` 顯示成 Gemini 備援,避免誤判 Gemini-first。
|
||||
- Schema smoke:`tests/test_market_intel_skeleton.py` 檢查 `Base.metadata` 內含 ADR-035 八張 `market_*` tables。
|
||||
- Desktop UI QA:本機只註冊 `market_intel_bp` 的 Flask harness 載入 `/market_intel`,確認 Phase 15、候選預覽、writer preview、安全 flags、點陣暖紙視覺正常,console error 0。
|
||||
- API QA:`/api/market_intel/schema_smoke` 通過 7 張表與 `market_platforms` 必要欄位檢查;`/api/market_intel/platform_seed_writer_plan` 回傳 4 筆 dry-run upsert preview,`writes_executed=false`,四平台皆 `blocked_dry_run_only`。
|
||||
- Narrow viewport UI QA:in-app browser 窄版畫面確認側欄收合、卡片不水平爆版、候選狀態顯示 `momo/pchome/coupang/shopee:planned`。
|
||||
- 測試:新增 `tests/test_market_intel_skeleton.py`,確認預設 flags 全關、adapter 不允許 network/write/scheduler,手動 discovery / candidate preview 預設不發 request,parser/scorer/confidence 診斷只輸出 JSON,UI 只使用 `fetch=false`,platform seed plan 只讀且需 gate。
|
||||
|
||||
【下次待辦】
|
||||
- 補 UI preview panel 真 390px 截圖 QA;本輪 in-app browser 不支援直接設定 viewport,且 data URL iframe QA 被瀏覽器安全策略阻擋,不做繞過。
|
||||
- 正式端 seed writer token-hardening drift:優先白名單同步 main 的一次性環境 token 版本,避免 API 暴露固定 approval token hint。
|
||||
- 下次市場情報 seed writer 只需保留定期 smoke,確認 `approval_token_hint` 不回歸且 `api_reads_approval_token=false`、`api_executes_cli=false`、`api_writes_database=false`。
|
||||
- 正式推版前需實際執行 worktree scope review、`python backup_system.py`、commit/push 目標變更、讀 deployment SOP 與 ADR-011,且只跑 `/health` 與市場情報頁 smoke。
|
||||
- 下一步才可在明確批准後設計 seed writer 的真寫入 implementation gate;預設環境不得寫 DB,也不得執行 migration。
|
||||
- 下一步才可在明確批准且具備真實 reviewed sample JSON 後做正式 DB 的 queue writer CLI 小流量 operator run;先用 run readiness、run package、operator drill、post-write smoke、run receipt、run closeout 與 queue review handoff 做順序與只讀驗證,預設 API/UI 不得寫 DB,也不得執行 migration。
|
||||
- 市場情報 UI 後續頁面必須沿用 V2 暖紙、暖墨、等寬數字與點陣風格,禁止複製巨型分析頁 template 模式。
|
||||
|
||||
================================================================================
|
||||
@@ -786,8 +1077,8 @@ gcloud compute ssh momo-server --zone=asia-east1-a \
|
||||
24. [CRITICAL] 移除硬編碼敏感資訊:
|
||||
- 檔案: config.py (第 17, 22, 26, 35, 40, 173 行)
|
||||
- 問題: 所有 API 金鑰、密碼、Token 直接寫在程式碼中
|
||||
• LOGIN_PASSWORD = "0936223270"
|
||||
• TELEGRAM_BOT_TOKEN = "8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg"
|
||||
• LOGIN_PASSWORD = "<LOGIN_PASSWORD>"
|
||||
• TELEGRAM_BOT_TOKEN = "<TELEGRAM_BOT_TOKEN>"
|
||||
• LINE_CHANNEL_ACCESS_TOKEN = "nD6MSXjB2FyB111zpT6Yik5B275mi6olHjjf94VnqN..."
|
||||
• EMAIL_HOST_PASSWORD = "jopokbhdpnnborjd"
|
||||
• NGROK_AUTH_TOKEN = "36e27NM5V7sUJ8QxJIAAWCp7sUv_3brtcrBarYvcP3SbvFKhF"
|
||||
@@ -984,3 +1275,15 @@ gcloud compute ssh momo-server --zone=asia-east1-a \
|
||||
* Top 3 商業洞察卡片。
|
||||
* 點擊卡片後的詳細列表 (Modal)。
|
||||
* 詳細列表的 Excel 匯出。
|
||||
|
||||
================================================================================
|
||||
【V10.490 OpenClaw 舊策略 Action 相容修補】
|
||||
================================================================================
|
||||
2026-05-31:
|
||||
- 正式日誌觀察到 ElephantAlpha 執行器收到 `agent=openclaw action=generate_market_strategy` 時,被誤判為未知步驟。
|
||||
- 本次修補將 `generate_market_strategy` 納入既有 OpenClaw advisory no-op 清單,與 `generate_resource_optimization_strategy` 一致:
|
||||
* 只記錄 skipped warning
|
||||
* 不觸發 circuit breaker
|
||||
* 不執行外部策略動作
|
||||
* 不變更任何價格或正式候選資料
|
||||
- 已補測試:舊策略 action 可安全跳過,未知 action 仍維持 fail-fast。
|
||||
|
||||
56
app.py
56
app.py
@@ -29,7 +29,7 @@ except OSError as e:
|
||||
|
||||
# ================= 🔧 2. 核心模組導入 =================
|
||||
try:
|
||||
from flask import Flask, render_template, jsonify, request, send_file, redirect, url_for, send_from_directory, flash, session
|
||||
from flask import Flask, render_template, jsonify, request, send_file, redirect, url_for, flash, session
|
||||
from werkzeug.utils import secure_filename
|
||||
from pyngrok import ngrok, conf
|
||||
import schedule
|
||||
@@ -58,7 +58,16 @@ except ImportError as e:
|
||||
|
||||
# ================= 🔧 3. 系統核心配置 =================
|
||||
# 從 config.py 匯入必要的設定
|
||||
from config import SYSTEM_VERSION, validate_critical_config
|
||||
from config import (
|
||||
SYSTEM_VERSION,
|
||||
WEBCRUMBS_BASE_URL,
|
||||
WEBCRUMBS_ENABLED,
|
||||
WEBCRUMBS_ASSET_UPSTREAM_URL,
|
||||
WEBCRUMBS_PLUGIN_BASE_URL,
|
||||
WEBCRUMBS_RUNTIME_URL,
|
||||
WEBCRUMBS_RUNTIME_VERSION,
|
||||
validate_critical_config,
|
||||
)
|
||||
|
||||
sys_log = SystemLogger("Web_Server").get_logger()
|
||||
|
||||
@@ -129,13 +138,22 @@ else:
|
||||
TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates')
|
||||
STATIC_DIR = os.path.join(BASE_DIR, 'web/static')
|
||||
|
||||
# 檢查關鍵模板是否存在
|
||||
if not os.path.exists(os.path.join(TEMPLATE_DIR, 'dashboard.html')):
|
||||
sys_log.warning(f"[Web] [Template] ⚠️ 警告: 找不到 dashboard.html | Path: {TEMPLATE_DIR}")
|
||||
# 檢查關鍵模板是否存在。正式商品看板已遷移到 V3 shell。
|
||||
if not os.path.exists(os.path.join(TEMPLATE_DIR, 'dashboard_v2.html')):
|
||||
sys_log.warning(f"[Web] [Template] ⚠️ 警告: 找不到 dashboard_v2.html | Path: {TEMPLATE_DIR}")
|
||||
|
||||
app = Flask(__name__,
|
||||
template_folder=TEMPLATE_DIR,
|
||||
static_folder=STATIC_DIR)
|
||||
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = timedelta(days=7)
|
||||
|
||||
|
||||
@app.url_defaults
|
||||
def add_static_asset_version(endpoint, values):
|
||||
"""讓靜態資源 URL 跟著系統版本破快取,搭配長快取標頭使用。"""
|
||||
if endpoint == 'static' and values.get('filename') and 'v' not in values:
|
||||
values['v'] = SYSTEM_VERSION
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 🔒 Flask 安全配置
|
||||
@@ -212,12 +230,12 @@ csrf.exempt(ai_bp) # ICAIM API 使用內部呼叫,不需要 CSRF
|
||||
sys_log.info("[Blueprint] ✅ AI 智慧文案系統 Blueprint 已註冊")
|
||||
|
||||
# ==========================================
|
||||
# 🔧 Blueprint 註冊 - CI/CD Dashboard
|
||||
# 🔧 Blueprint 註冊 - 部署監控
|
||||
# ==========================================
|
||||
from routes.cicd_routes import cicd_bp
|
||||
app.register_blueprint(cicd_bp)
|
||||
csrf.exempt(cicd_bp) # CI/CD API doesn't need CSRF
|
||||
sys_log.info("[Blueprint] CI/CD Dashboard Blueprint registered")
|
||||
csrf.exempt(cicd_bp) # 部署監控 API 使用內部呼叫,不需要 CSRF
|
||||
sys_log.info("[Blueprint] ✅ 部署監控 Blueprint 已註冊")
|
||||
|
||||
# ==========================================
|
||||
# 🔧 Blueprint 註冊 - Code Review 系統
|
||||
@@ -376,6 +394,9 @@ sys_log.info("[Blueprint] ✅ pchome_bp 已註冊")
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
app.register_blueprint(market_intel_bp)
|
||||
sys_log.info("[Blueprint] ✅ market_intel_bp 已註冊")
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
sys_log.info("[Blueprint] ✅ market_intel_review_bp 已註冊")
|
||||
|
||||
# V-Fix: 註冊 slugify 函數供模板使用(實作搬至 utils/text_helpers.py)
|
||||
from utils.text_helpers import slugify # noqa: E402
|
||||
@@ -400,6 +421,7 @@ EXPECTED_METADATA_TABLES = {
|
||||
'market_platforms', 'market_campaigns', 'market_campaign_snapshots',
|
||||
'market_campaign_products', 'market_product_price_history',
|
||||
'market_product_matches', 'market_crawler_runs',
|
||||
'market_alert_review_queue',
|
||||
}
|
||||
|
||||
|
||||
@@ -414,18 +436,24 @@ verify_metadata_tables()
|
||||
# ==========================================
|
||||
# 🔧 全域模板變數注入 (Context Processor)
|
||||
# ==========================================
|
||||
from config import METABASE_URL, GRIST_URL
|
||||
|
||||
@app.context_processor
|
||||
def inject_global_vars():
|
||||
"""注入全域變數到所有模板"""
|
||||
return {
|
||||
'metabase_url': METABASE_URL,
|
||||
'grist_url': GRIST_URL,
|
||||
'metabase_url': '/metabase',
|
||||
'grist_url': '/grist',
|
||||
'datetime_now': datetime.now(TAIPEI_TZ).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'webcrumbs_config': {
|
||||
'enabled': WEBCRUMBS_ENABLED,
|
||||
'base_url': WEBCRUMBS_BASE_URL,
|
||||
'runtime_url': WEBCRUMBS_RUNTIME_URL,
|
||||
'runtime_version': WEBCRUMBS_RUNTIME_VERSION,
|
||||
'plugin_base_url': WEBCRUMBS_PLUGIN_BASE_URL,
|
||||
'asset_upstream_url': WEBCRUMBS_ASSET_UPSTREAM_URL,
|
||||
},
|
||||
}
|
||||
|
||||
sys_log.info("[Template] ✅ 全域模板變數已注入 (metabase_url, grist_url)")
|
||||
sys_log.info("[Template] ✅ 全域模板變數已注入 (metabase_url, grist_url, webcrumbs_config)")
|
||||
|
||||
# ================= 🛠️ V9.72: 分類設定管理核心 =================
|
||||
CATEGORIES_JSON_PATH = os.path.join(BASE_DIR, 'data', 'categories.json')
|
||||
@@ -615,6 +643,8 @@ def refresh_session():
|
||||
在每次請求時自動刷新 Session,避免長時間閒置後突然斷線
|
||||
只要用戶有任何操作,Session 就會自動延長
|
||||
"""
|
||||
if request.endpoint == 'static':
|
||||
return
|
||||
if session.get('logged_in'):
|
||||
session.modified = True # 標記 Session 已修改,觸發 Cookie 更新
|
||||
|
||||
|
||||
104
config.py
104
config.py
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
from urllib.parse import urlparse
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# 載入 .env 環境變數
|
||||
@@ -111,6 +112,57 @@ EMAIL_RECEIVER = os.getenv('EMAIL_RECEIVER', '')
|
||||
# ==========================================
|
||||
PUBLIC_URL = os.getenv('PUBLIC_URL', 'https://mo.wooo.work')
|
||||
|
||||
|
||||
def _env_bool(name: str, default: str = 'false') -> bool:
|
||||
return os.getenv(name, default).strip().lower() in {'1', 'true', 'yes', 'on'}
|
||||
|
||||
|
||||
def _safe_public_http_url(value: str) -> str:
|
||||
"""只接受 http(s) URL,避免模板把 javascript/data URI 輸出成 script src。"""
|
||||
candidate = (value or '').strip().rstrip('/')
|
||||
if not candidate:
|
||||
return ''
|
||||
parsed = urlparse(candidate)
|
||||
if parsed.scheme not in {'http', 'https'} or not parsed.netloc:
|
||||
return ''
|
||||
return candidate
|
||||
|
||||
|
||||
def _safe_webcrumbs_reference(value: str) -> str:
|
||||
"""允許 http(s) URL 或 momo-pro 同源絕對路徑,供 script/plugin URL 使用。"""
|
||||
candidate = (value or '').strip().rstrip('/')
|
||||
if not candidate:
|
||||
return ''
|
||||
if candidate.startswith('/') and not candidate.startswith('//') and '\\' not in candidate:
|
||||
return candidate
|
||||
return _safe_public_http_url(candidate)
|
||||
|
||||
|
||||
# Webcrumbs 共用 microfrontend runtime。
|
||||
# 預設透過 momo-pro 同源 proxy 讀 188 Shared UI Hub,避免正式頁面被跨域 TLS 狀態卡住。
|
||||
WEBCRUMBS_ENABLED = _env_bool('WEBCRUMBS_ENABLED', 'true')
|
||||
WEBCRUMBS_BASE_URL = _safe_public_http_url(
|
||||
os.getenv('WEBCRUMBS_BASE_URL', 'https://webcrumbs.wooo.work')
|
||||
)
|
||||
WEBCRUMBS_RUNTIME_VERSION = os.getenv('WEBCRUMBS_RUNTIME_VERSION', 'shared-ui-poc-0.1.0').strip().strip('/')
|
||||
WEBCRUMBS_RUNTIME_PATH = os.getenv(
|
||||
'WEBCRUMBS_RUNTIME_PATH',
|
||||
'/webcrumbs-assets/loader/webcrumbs-compatible-loader.js',
|
||||
).strip()
|
||||
_webcrumbs_runtime_url = os.getenv('WEBCRUMBS_RUNTIME_URL', '').strip()
|
||||
WEBCRUMBS_RUNTIME_URL = _safe_webcrumbs_reference(_webcrumbs_runtime_url)
|
||||
if not WEBCRUMBS_RUNTIME_URL:
|
||||
WEBCRUMBS_RUNTIME_URL = _safe_webcrumbs_reference(WEBCRUMBS_RUNTIME_PATH)
|
||||
WEBCRUMBS_PLUGIN_BASE_URL = _safe_webcrumbs_reference(
|
||||
os.getenv(
|
||||
'WEBCRUMBS_PLUGIN_BASE_URL',
|
||||
'/webcrumbs-assets/plugins',
|
||||
)
|
||||
)
|
||||
WEBCRUMBS_ASSET_UPSTREAM_URL = _safe_public_http_url(
|
||||
os.getenv('WEBCRUMBS_ASSET_UPSTREAM_URL', 'http://192.168.0.188:18088')
|
||||
)
|
||||
|
||||
# ==========================================
|
||||
# 市場情報模組設定(預設全部關閉)
|
||||
# ==========================================
|
||||
@@ -229,6 +281,29 @@ GRIST_URL = os.getenv('GRIST_URL', '') # Grist 資料協作連結
|
||||
# ==========================================
|
||||
# AI 服務設定
|
||||
# ==========================================
|
||||
|
||||
_APPROVED_OLLAMA_HOST_SUBSTRINGS = (
|
||||
'34.87.90.216:11434',
|
||||
'34.21.145.224:11434',
|
||||
'192.168.0.111:11434',
|
||||
'192.168.0.110:11435',
|
||||
'192.168.0.110:11436',
|
||||
)
|
||||
|
||||
|
||||
def _static_approved_ollama_env(name: str, default: str = '') -> str:
|
||||
"""Import-time safe Ollama host env reader; never probes network."""
|
||||
value = os.getenv(name, '').strip()
|
||||
if value and any(approved in value for approved in _APPROVED_OLLAMA_HOST_SUBSTRINGS):
|
||||
return value
|
||||
return default
|
||||
|
||||
|
||||
_STATIC_OLLAMA_PRIMARY = _static_approved_ollama_env(
|
||||
'OLLAMA_HOST_PRIMARY',
|
||||
'http://34.87.90.216:11434',
|
||||
)
|
||||
|
||||
# Hermes AI Service (競價情報分析)
|
||||
# V-New (ADR-027 Phase 2):所有 host 解析必須 lazy,禁止 import-time freeze。
|
||||
# 理由:import 時 GCP 還沒探測(resolve_ollama_host 內部 cache 120s),
|
||||
@@ -288,28 +363,35 @@ def get_ollama_host():
|
||||
return 'http://192.168.0.111:11434'
|
||||
|
||||
|
||||
# 向下相容:舊 caller 仍可 `from config import HERMES_URL` 取得當下解析值。
|
||||
# ※ 重要:這仍是 import-time freeze。新 caller 應改用 `get_hermes_url()`。
|
||||
HERMES_URL = get_hermes_url()
|
||||
# 向下相容:舊 caller 仍可 `from config import HERMES_URL`,但此常數不得
|
||||
# import-time probe,也不得在 GCP 短暫不可用時 freeze 到 111;新 caller 應
|
||||
# 改用 `get_hermes_url()` 或 `OllamaService` 取得動態三主機級聯結果。
|
||||
HERMES_URL = _static_approved_ollama_env('HERMES_URL', _STATIC_OLLAMA_PRIMARY)
|
||||
HERMES_TIMEOUT = int(os.getenv('HERMES_TIMEOUT', '120')) # 秒;批量 300 筆預估 ~90s
|
||||
|
||||
# Embedding 服務(ADR-003 對齊:embedding 走 Hermes 主機,內網免認證)
|
||||
# 向下相容;新 caller 應改用 `get_embedding_host()`
|
||||
EMBEDDING_HOST = get_embedding_host()
|
||||
# 向下相容;新 caller 應改用 `get_embedding_host()` 或
|
||||
# `OllamaService.generate_embedding()`,不可依賴 import-time 探測結果。
|
||||
EMBEDDING_HOST = _static_approved_ollama_env('EMBEDDING_HOST', HERMES_URL)
|
||||
EMBEDDING_TIMEOUT = int(os.getenv('EMBEDDING_TIMEOUT', os.getenv('OLLAMA_EMBED_TIMEOUT', '45')))
|
||||
|
||||
# Ollama 本地 AI 服務
|
||||
# ADR-027 Phase 2:OLLAMA_HOST 改為 lazy resolve,禁止寫死 nginx URL 繞過 GCP 優先策略。
|
||||
# 舊行為:寫死 'https://ollama.wooo.work/ollama' → 任何 import 都跳過 GCP 探測。
|
||||
# 新行為:env OLLAMA_HOST 優先;否則走 resolve_ollama_host()(GCP 優先 / 111 備援)。
|
||||
# 向下相容:保留 OLLAMA_HOST module attribute,但於 import 時呼叫 get_ollama_host()。
|
||||
# 新行為:env OLLAMA_HOST 優先;否則動態 caller 走 resolve_ollama_host()(GCP 優先 / 111 備援)。
|
||||
# 向下相容:保留 OLLAMA_HOST module attribute,但不再於 import 時呼叫 get_ollama_host()。
|
||||
# 新 caller 應改用 `from config import get_ollama_host` + `host = get_ollama_host()`。
|
||||
OLLAMA_HOST = get_ollama_host()
|
||||
OLLAMA_HOST = _static_approved_ollama_env('OLLAMA_HOST', _STATIC_OLLAMA_PRIMARY)
|
||||
OLLAMA_MODEL = os.getenv('OLLAMA_MODEL', 'gemma3:4b')
|
||||
|
||||
# Google Gemini AI 雲端服務
|
||||
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY', '')
|
||||
GEMINI_MODEL = os.getenv('GEMINI_MODEL', 'gemini-1.5-flash')
|
||||
# Gemini is emergency fallback only. The hard kill switch defaults to true, so
|
||||
# GEMINI_API_KEY/GEMINI_FALLBACK_ENABLED cannot create paid egress by accident.
|
||||
GEMINI_API_HARD_DISABLED = os.getenv('GEMINI_API_HARD_DISABLED', 'true')
|
||||
GEMINI_FALLBACK_ENABLED = os.getenv('GEMINI_FALLBACK_ENABLED', 'false')
|
||||
GEMINI_ALLOWED_CONTEXTS = os.getenv('GEMINI_ALLOWED_CONTEXTS', '')
|
||||
|
||||
# 預設 AI 提供者: 'ollama' (本地免費) 或 'gemini' (雲端付費)
|
||||
AI_PROVIDER = os.getenv('AI_PROVIDER', 'ollama')
|
||||
@@ -320,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.151"
|
||||
SYSTEM_VERSION = "V10.639"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
@@ -331,4 +413,8 @@ def validate_critical_config():
|
||||
for var in optional_vars:
|
||||
if not os.getenv(var):
|
||||
warnings.append(f"[Config] 選用設定 {var} 未設,部分功能可能停用")
|
||||
if WEBCRUMBS_ENABLED and not WEBCRUMBS_RUNTIME_URL:
|
||||
warnings.append("[Config] WEBCRUMBS_ENABLED=true 但 WEBCRUMBS_RUNTIME_URL 無效,Webcrumbs runtime 將不會載入")
|
||||
if WEBCRUMBS_ENABLED and WEBCRUMBS_RUNTIME_URL.startswith('/webcrumbs-assets/') and not WEBCRUMBS_ASSET_UPSTREAM_URL:
|
||||
warnings.append("[Config] Webcrumbs 使用同源 asset proxy,但 WEBCRUMBS_ASSET_UPSTREAM_URL 無效")
|
||||
return warnings
|
||||
|
||||
107
database/external_market_models.py
Normal file
107
database/external_market_models.py
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""外部市場來源與報價的正規化 ORM models。"""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
Float,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from database.models import Base
|
||||
|
||||
|
||||
TAIPEI_TZ = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def taipei_now():
|
||||
return datetime.now(TAIPEI_TZ).replace(tzinfo=None)
|
||||
|
||||
|
||||
class ExternalMarketSource(Base):
|
||||
"""外部市場資料來源,例如 MOMO 參考價、蝦皮 API、酷澎 CSV。"""
|
||||
|
||||
__tablename__ = "external_market_sources"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
code = Column(String(80), unique=True, nullable=False, index=True)
|
||||
display_name = Column(String(160), nullable=False)
|
||||
platform_code = Column(String(80), nullable=False, index=True)
|
||||
source_kind = Column(String(60), nullable=False, index=True)
|
||||
status = Column(String(40), default="paused", nullable=False, index=True)
|
||||
enabled = Column(Boolean, default=False, nullable=False)
|
||||
write_enabled = Column(Boolean, default=False, nullable=False)
|
||||
allowed_input_methods_json = Column(Text)
|
||||
quality_policy_json = Column(Text)
|
||||
plain_note = Column(Text)
|
||||
created_at = Column(DateTime, default=taipei_now, nullable=False)
|
||||
updated_at = Column(DateTime, default=taipei_now, onupdate=taipei_now, nullable=False)
|
||||
|
||||
offers = relationship("ExternalOffer", back_populates="source")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_external_market_sources_status", "status", "enabled"),
|
||||
)
|
||||
|
||||
|
||||
class ExternalOffer(Base):
|
||||
"""正規化後的外部商品報價。"""
|
||||
|
||||
__tablename__ = "external_offers"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
source_code = Column(String(80), ForeignKey("external_market_sources.code"), nullable=False, index=True)
|
||||
platform_code = Column(String(80), nullable=False, index=True)
|
||||
source_product_id = Column(String(220), nullable=False, index=True)
|
||||
source_offer_key = Column(String(260), nullable=False)
|
||||
title = Column(Text, nullable=False)
|
||||
brand = Column(String(180), index=True)
|
||||
category_text = Column(String(320), index=True)
|
||||
product_url = Column(Text)
|
||||
image_url = Column(Text)
|
||||
price = Column(Float)
|
||||
original_price = Column(Float)
|
||||
currency = Column(String(12), default="TWD", nullable=False)
|
||||
stock_status = Column(String(80), index=True)
|
||||
sold_count = Column(Integer)
|
||||
rating = Column(Float)
|
||||
review_count = Column(Integer)
|
||||
observed_at = Column(DateTime, default=taipei_now, nullable=False, index=True)
|
||||
expires_at = Column(DateTime, index=True)
|
||||
ingestion_method = Column(String(60), nullable=False, index=True)
|
||||
connector_key = Column(String(120), index=True)
|
||||
pchome_product_id = Column(String(120), index=True)
|
||||
momo_sku = Column(String(80), index=True)
|
||||
match_status = Column(String(40), default="unmatched", nullable=False, index=True)
|
||||
quality_score = Column(Float, default=0.0, nullable=False)
|
||||
data_quality_status = Column(String(40), default="needs_review", nullable=False, index=True)
|
||||
quality_notes_json = Column(Text)
|
||||
raw_payload_json = Column(Text)
|
||||
created_at = Column(DateTime, default=taipei_now, nullable=False)
|
||||
updated_at = Column(DateTime, default=taipei_now, onupdate=taipei_now, nullable=False)
|
||||
|
||||
source = relationship("ExternalMarketSource", back_populates="offers")
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"source_code",
|
||||
"source_product_id",
|
||||
"observed_at",
|
||||
"ingestion_method",
|
||||
name="uq_external_offer_source_product_observed",
|
||||
),
|
||||
Index("idx_external_offers_source_seen", "source_code", "observed_at"),
|
||||
Index("idx_external_offers_platform_product", "platform_code", "source_product_id"),
|
||||
Index("idx_external_offers_pchome_product", "pchome_product_id", "source_code"),
|
||||
Index("idx_external_offers_match_quality", "match_status", "data_quality_status", "quality_score"),
|
||||
)
|
||||
@@ -35,6 +35,7 @@ from .autoheal_models import ( # noqa: F401 - ADR-013 AIOps 自動修復表
|
||||
from .import_models import ImportJob, ImportConfig # noqa: F401 - 確保 import_jobs/import_config 被 Base.metadata 管理
|
||||
from .notification_models import NotificationTemplate # noqa: F401 - 確保 notification_templates 表被 Base.metadata 管理
|
||||
from .ppt_reports import PPTReport # noqa: F401 - 確保 ppt_reports 表被 Base.metadata 管理
|
||||
from .ppt_generation_runs import PPTGenerationRun # noqa: F401 - 確保 ppt_generation_runs 表被 Base.metadata 管理
|
||||
from .vendor_models import VendorStockout, VendorList, VendorEmail, EmailSendLog # noqa: F401 - 確保 vendor 表被 Base.metadata 管理
|
||||
from .realtime_sales_models import RealtimeSalesMonthly # noqa: F401 - 確保 realtime_sales_monthly 被 Base.metadata 管理
|
||||
from .market_intel_models import ( # noqa: F401 - ADR-035 market_* 表
|
||||
@@ -45,7 +46,9 @@ from .market_intel_models import ( # noqa: F401 - ADR-035 market_* 表
|
||||
MarketProductPriceHistory,
|
||||
MarketProductMatch,
|
||||
MarketCrawlerRun,
|
||||
MarketAlertReviewQueue,
|
||||
)
|
||||
from .external_market_models import ExternalMarketSource, ExternalOffer # noqa: F401 - 外部市場正規化表
|
||||
|
||||
# 🚩 導入優化後的日誌管理模組
|
||||
from utils.logger_manager import SystemLogger
|
||||
|
||||
@@ -222,3 +222,46 @@ class MarketCrawlerRun(Base):
|
||||
Index("idx_market_crawler_run_platform_time", "platform_code", "started_at"),
|
||||
Index("idx_market_crawler_run_status_time", "status", "started_at"),
|
||||
)
|
||||
|
||||
|
||||
class MarketAlertReviewQueue(Base):
|
||||
"""市場機會與威脅告警的人工審核佇列。"""
|
||||
|
||||
__tablename__ = "market_alert_review_queue"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
alert_candidate_id = Column(String(120), nullable=False, unique=True, index=True)
|
||||
review_state = Column(String(40), default="draft", nullable=False, index=True)
|
||||
priority_lane = Column(String(40), default="watch", nullable=False, index=True)
|
||||
threshold_level = Column(String(40), nullable=False, index=True)
|
||||
total_score = Column(Float, default=0.0, nullable=False)
|
||||
evidence_bundle_id = Column(String(120), nullable=False, index=True)
|
||||
dedupe_key = Column(String(240), nullable=False)
|
||||
source_batch_id = Column(String(80), nullable=False, index=True)
|
||||
campaign_id = Column(Integer, ForeignKey("market_campaigns.id"), index=True)
|
||||
market_product_id = Column(Integer, ForeignKey("market_campaign_products.id"), index=True)
|
||||
momo_i_code = Column(String(50), index=True)
|
||||
reviewer_identity = Column(String(120))
|
||||
review_action = Column(String(60))
|
||||
review_reason = Column(Text)
|
||||
reviewed_at = Column(DateTime)
|
||||
previous_state = Column(String(40))
|
||||
next_state = Column(String(40))
|
||||
created_at = Column(DateTime, default=taipei_now, nullable=False)
|
||||
updated_at = Column(DateTime, default=taipei_now, onupdate=taipei_now, nullable=False)
|
||||
metadata_json = Column(Text)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"idx_market_alert_review_queue_state_priority",
|
||||
"review_state",
|
||||
"priority_lane",
|
||||
"created_at",
|
||||
),
|
||||
Index("ux_market_alert_review_queue_dedupe", "dedupe_key", unique=True),
|
||||
Index(
|
||||
"idx_market_alert_review_queue_bundle",
|
||||
"evidence_bundle_id",
|
||||
"source_batch_id",
|
||||
),
|
||||
)
|
||||
|
||||
32
database/ppt_generation_runs.py
Normal file
32
database/ppt_generation_runs.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""PPT 定期產出執行紀錄模型。"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Column, DateTime, Integer, String, Text
|
||||
|
||||
from database.models import Base
|
||||
|
||||
|
||||
class PPTGenerationRun(Base):
|
||||
"""每次日/週/月/季/半年/年度 PPT 產出嘗試。"""
|
||||
|
||||
__tablename__ = "ppt_generation_runs"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
schedule_kind = Column(String(40), nullable=False, index=True)
|
||||
report_type = Column(String(50), nullable=False, index=True)
|
||||
target_label = Column(String(160))
|
||||
status = Column(String(30), nullable=False)
|
||||
parameters_json = Column(Text)
|
||||
file_path = Column(String(500))
|
||||
file_size = Column(Integer)
|
||||
error_msg = Column(Text)
|
||||
result_payload = Column(Text)
|
||||
started_at = Column(DateTime, default=datetime.now, index=True)
|
||||
finished_at = Column(DateTime)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"<PPTGenerationRun(kind={self.schedule_kind}, "
|
||||
f"type={self.report_type}, status={self.status})>"
|
||||
)
|
||||
@@ -71,5 +71,40 @@ def repair_database_schema():
|
||||
for col_name, ddl in promo_columns:
|
||||
_ensure_column(engine, text, 'promo_products', col_name, ddl)
|
||||
|
||||
# V10.328: PChome/MOMO 比價診斷欄位。正式價差與待審嘗試都需可回溯
|
||||
# URL、圖片、庫存與 matcher 結構化原因,避免「待比對」無法被人工處理。
|
||||
json_type = 'JSONB' if DATABASE_TYPE == 'postgresql' else 'TEXT'
|
||||
competitor_price_columns = [
|
||||
('competitor_product_url', "ALTER TABLE competitor_prices ADD COLUMN competitor_product_url TEXT"),
|
||||
('competitor_image_url', "ALTER TABLE competitor_prices ADD COLUMN competitor_image_url TEXT"),
|
||||
('competitor_stock', "ALTER TABLE competitor_prices ADD COLUMN competitor_stock INTEGER"),
|
||||
('match_diagnostic_json', f"ALTER TABLE competitor_prices ADD COLUMN match_diagnostic_json {json_type}"),
|
||||
('comparison_mode', "ALTER TABLE competitor_prices ADD COLUMN comparison_mode VARCHAR(40)"),
|
||||
('hard_veto', "ALTER TABLE competitor_prices ADD COLUMN hard_veto BOOLEAN"),
|
||||
('diagnostic_codes', f"ALTER TABLE competitor_prices ADD COLUMN diagnostic_codes {json_type}"),
|
||||
]
|
||||
competitor_history_columns = [
|
||||
('competitor_product_url', "ALTER TABLE competitor_price_history ADD COLUMN competitor_product_url TEXT"),
|
||||
('competitor_image_url', "ALTER TABLE competitor_price_history ADD COLUMN competitor_image_url TEXT"),
|
||||
('competitor_stock', "ALTER TABLE competitor_price_history ADD COLUMN competitor_stock INTEGER"),
|
||||
('match_diagnostic_json', f"ALTER TABLE competitor_price_history ADD COLUMN match_diagnostic_json {json_type}"),
|
||||
('comparison_mode', "ALTER TABLE competitor_price_history ADD COLUMN comparison_mode VARCHAR(40)"),
|
||||
('hard_veto', "ALTER TABLE competitor_price_history ADD COLUMN hard_veto BOOLEAN"),
|
||||
('diagnostic_codes', f"ALTER TABLE competitor_price_history ADD COLUMN diagnostic_codes {json_type}"),
|
||||
]
|
||||
competitor_attempt_columns = [
|
||||
('competitor_product_url', "ALTER TABLE competitor_match_attempts ADD COLUMN competitor_product_url TEXT"),
|
||||
('competitor_image_url', "ALTER TABLE competitor_match_attempts ADD COLUMN competitor_image_url TEXT"),
|
||||
('competitor_stock', "ALTER TABLE competitor_match_attempts ADD COLUMN competitor_stock INTEGER"),
|
||||
('match_diagnostic_json', f"ALTER TABLE competitor_match_attempts ADD COLUMN match_diagnostic_json {json_type}"),
|
||||
('comparison_mode', "ALTER TABLE competitor_match_attempts ADD COLUMN comparison_mode VARCHAR(40)"),
|
||||
('hard_veto', "ALTER TABLE competitor_match_attempts ADD COLUMN hard_veto BOOLEAN"),
|
||||
('diagnostic_codes', f"ALTER TABLE competitor_match_attempts ADD COLUMN diagnostic_codes {json_type}"),
|
||||
]
|
||||
for table_columns in (competitor_price_columns, competitor_history_columns, competitor_attempt_columns):
|
||||
for col_name, ddl in table_columns:
|
||||
table_name = ddl.split()[2]
|
||||
_ensure_column(engine, text, table_name, col_name, ddl)
|
||||
|
||||
except Exception as e:
|
||||
_log.error(f"[Database] [Schema] ❌ 資料庫修復失敗 | Error: {e}")
|
||||
|
||||
@@ -53,7 +53,7 @@ HARBOR_PASSWORD="${HARBOR_PASSWORD:-Wooo_Harbor_2026}"
|
||||
HARBOR_PROJECT="${HARBOR_PROJECT:-wooo}"
|
||||
|
||||
# Telegram 設定
|
||||
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg}"
|
||||
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-<TELEGRAM_BOT_TOKEN>}"
|
||||
TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-5619078117}"
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -61,6 +61,7 @@ services:
|
||||
- ./config.py:/app/config.py:ro
|
||||
- ./app.py:/app/app.py:ro
|
||||
- ./auth.py:/app/auth.py:ro
|
||||
- ./docker-compose.mcp.yml:/app/docker-compose.mcp.yml:ro
|
||||
- ./gunicorn.conf.py:/app/gunicorn.conf.py:ro
|
||||
- ./scheduler.py:/app/scheduler.py:ro
|
||||
- ./scripts:/app/scripts:ro
|
||||
@@ -78,8 +79,8 @@ services:
|
||||
- FLASK_ENV=production
|
||||
- PYTHONUNBUFFERED=1
|
||||
- TZ=Asia/Taipei
|
||||
- METABASE_URL=https://mo.wooo.work/metabase
|
||||
- GRIST_URL=https://grist.wooo.work
|
||||
- METABASE_URL=/metabase
|
||||
- GRIST_URL=/grist
|
||||
# 關閉登入驗證(開發/測試用,生產環境預設啟用登入)
|
||||
- DISABLE_LOGIN=${DISABLE_LOGIN:-false}
|
||||
# 資料庫設定: Docker 環境使用 PostgreSQL
|
||||
@@ -90,11 +91,17 @@ services:
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-momo_analytics}
|
||||
# Ollama 主機:GCP-A → GCP-B → 111 自動備援(ADR-028)
|
||||
- OLLAMA_HOST_PRIMARY=${OLLAMA_HOST_PRIMARY:-http://34.143.170.20:11434}
|
||||
- OLLAMA_HOST_PRIMARY=${OLLAMA_HOST_PRIMARY:-http://34.87.90.216:11434}
|
||||
- OLLAMA_HOST_SECONDARY=${OLLAMA_HOST_SECONDARY:-http://34.21.145.224:11434}
|
||||
- OLLAMA_HOST_FALLBACK=${OLLAMA_HOST_FALLBACK:-http://192.168.0.111:11434}
|
||||
# EMBEDDING_HOST 若未設定,由 resolve_ollama_host() 自動決定(三主機級聯)
|
||||
- EMBEDDING_HOST=${EMBEDDING_HOST:-}
|
||||
# Gemini 只能作緊急備援;即使 .env 有 API key,預設也不得產生付費出站。
|
||||
- GEMINI_API_HARD_DISABLED=${GEMINI_API_HARD_DISABLED:-true}
|
||||
- GEMINI_FALLBACK_ENABLED=${GEMINI_FALLBACK_ENABLED:-false}
|
||||
# PPT 視覺 QA + 線上預覽:需要容器內 LibreOffice(Dockerfile 安裝 libreoffice-impress)
|
||||
- PPT_VISION_ENABLED=${PPT_VISION_ENABLED:-true}
|
||||
- PPT_VISION_MODEL=${PPT_VISION_MODEL:-minicpm-v:latest}
|
||||
# ADR-020: Code Review 全自動修復主開關
|
||||
# 預設 true(任何 finding 一律觸發 AiderHeal),可在 .env 顯式設 false 即時切斷
|
||||
- CODE_REVIEW_AUTO_FIX_ENABLED=${CODE_REVIEW_AUTO_FIX_ENABLED:-true}
|
||||
@@ -218,10 +225,15 @@ services:
|
||||
- USE_POSTGRESQL=true
|
||||
- POSTGRES_PORT=5432
|
||||
# Ollama 主機:GCP-A → GCP-B → 111 自動備援(ADR-028)
|
||||
- OLLAMA_HOST_PRIMARY=${OLLAMA_HOST_PRIMARY:-http://34.143.170.20:11434}
|
||||
- OLLAMA_HOST_PRIMARY=${OLLAMA_HOST_PRIMARY:-http://34.87.90.216:11434}
|
||||
- OLLAMA_HOST_SECONDARY=${OLLAMA_HOST_SECONDARY:-http://34.21.145.224:11434}
|
||||
- OLLAMA_HOST_FALLBACK=${OLLAMA_HOST_FALLBACK:-http://192.168.0.111:11434}
|
||||
- EMBEDDING_HOST=${EMBEDDING_HOST:-}
|
||||
# Gemini 只能作緊急備援;即使 .env 有 API key,預設也不得產生付費出站。
|
||||
- GEMINI_API_HARD_DISABLED=${GEMINI_API_HARD_DISABLED:-true}
|
||||
- GEMINI_FALLBACK_ENABLED=${GEMINI_FALLBACK_ENABLED:-false}
|
||||
- PPT_VISION_ENABLED=${PPT_VISION_ENABLED:-true}
|
||||
- PPT_VISION_MODEL=${PPT_VISION_MODEL:-minicpm-v:latest}
|
||||
env_file:
|
||||
- .env
|
||||
command: ["python", "run_scheduler.py"]
|
||||
@@ -276,10 +288,13 @@ services:
|
||||
- USE_POSTGRESQL=true
|
||||
- POSTGRES_PORT=5432
|
||||
# Ollama 主機:GCP-A → GCP-B → 111 自動備援(ADR-028)
|
||||
- OLLAMA_HOST_PRIMARY=${OLLAMA_HOST_PRIMARY:-http://34.143.170.20:11434}
|
||||
- OLLAMA_HOST_PRIMARY=${OLLAMA_HOST_PRIMARY:-http://34.87.90.216:11434}
|
||||
- OLLAMA_HOST_SECONDARY=${OLLAMA_HOST_SECONDARY:-http://34.21.145.224:11434}
|
||||
- OLLAMA_HOST_FALLBACK=${OLLAMA_HOST_FALLBACK:-http://192.168.0.111:11434}
|
||||
- EMBEDDING_HOST=${EMBEDDING_HOST:-}
|
||||
# Gemini 只能作緊急備援;即使 .env 有 API key,預設也不得產生付費出站。
|
||||
- GEMINI_API_HARD_DISABLED=${GEMINI_API_HARD_DISABLED:-true}
|
||||
- GEMINI_FALLBACK_ENABLED=${GEMINI_FALLBACK_ENABLED:-false}
|
||||
env_file:
|
||||
- .env
|
||||
command: ["python", "run_telegram_bot.py"]
|
||||
@@ -765,7 +780,7 @@ services:
|
||||
- GRIST_SUPPORT_ANON=true
|
||||
- GRIST_FORCE_LOGIN=false
|
||||
- GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts
|
||||
- APP_HOME_URL=https://grist.wooo.work
|
||||
- APP_HOME_URL=${GRIST_APP_HOME_URL:-https://mo.wooo.work/grist}
|
||||
- TZ=Asia/Taipei
|
||||
networks:
|
||||
- momo-network
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|------|-----|
|
||||
| URL | https://monitor.wooo.work/superset/ |
|
||||
| 帳號 | admin |
|
||||
| 密碼 | Wooo_Superset_2026 |
|
||||
| 密碼 | <SUPERSET_ADMIN_PASSWORD> |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|------|-----|
|
||||
| URL | https://monitor.wooo.work/superset/ |
|
||||
| 帳號 | admin |
|
||||
| 密碼 | Wooo_Superset_2026 |
|
||||
| 密碼 | <SUPERSET_ADMIN_PASSWORD> |
|
||||
| 資料庫 | MOMO_UAT |
|
||||
|
||||
---
|
||||
|
||||
@@ -66,7 +66,7 @@ chmod +x deploy.sh
|
||||
| 內部 URL | `http://127.0.0.1:8088` |
|
||||
| 外部 URL | `https://monitor.wooo.work/superset/` |
|
||||
| 帳號 | `admin` |
|
||||
| 密碼 | `Wooo_Superset_2026` |
|
||||
| 密碼 | `<SUPERSET_ADMIN_PASSWORD>` |
|
||||
|
||||
## Nginx 配置
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ deploy() {
|
||||
echo "外部訪問: https://monitor.wooo.work/superset/"
|
||||
echo ""
|
||||
echo "登入帳號: admin"
|
||||
echo "登入密碼: Wooo_Superset_2026"
|
||||
echo "登入密碼: <SUPERSET_ADMIN_PASSWORD>"
|
||||
echo ""
|
||||
echo "下一步:"
|
||||
echo " 1. 設定 Nginx 反向代理"
|
||||
|
||||
@@ -64,7 +64,7 @@ services:
|
||||
fi
|
||||
done &&
|
||||
superset db upgrade &&
|
||||
superset fab create-admin --username admin --firstname Admin --lastname User --email admin@wooo.work --password Wooo_Superset_2026 || true &&
|
||||
superset fab create-admin --username admin --firstname Admin --lastname User --email admin@wooo.work --password <SUPERSET_ADMIN_PASSWORD> || true &&
|
||||
superset init &&
|
||||
echo 'Superset 啟動中...' &&
|
||||
gunicorn --bind 0.0.0.0:8088 --workers 4 --timeout 120 --access-logfile - 'superset.app:create_app()'
|
||||
|
||||
@@ -1,23 +1,97 @@
|
||||
# MOMO PRO — AI 競價情報模組 Single Source of Truth
|
||||
# PChome 業績成長自動化作戰系統 — AI 競價情報模組 Single Source of Truth
|
||||
|
||||
> **最後更新**: 2026-05-13 (台北時間)
|
||||
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯,Gemini 僅備援 / 鎖定場景
|
||||
> **適用版本**: V10.129
|
||||
> **最後更新**: 2026-06-18 (台北時間)
|
||||
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯;PChome 後台業績匯入韌性已補強;產品定位正名為「PChome 業績成長自動化作戰系統」;外部市場來源正規化層、自動同步、作戰清單與價格參考表優先讀取、CSV 備援預檢、前台操作入口、高可見頁面繁中化守門、比價/作戰 UI 工作台化、GCP embedding 熔斷延後處理、110 proxy rescue 與 direct host health skip 已建立
|
||||
> **適用版本**: V10.627
|
||||
|
||||
---
|
||||
|
||||
## 零、LLM 路由紅線(2026-05-12)
|
||||
|
||||
- 所有 AI Agent、LLM 推理與 embedding 預設必須走 Ollama 三主機級聯:GCP-A `34.143.170.20:11434` → GCP-B `34.21.145.224:11434` → 111 `192.168.0.111:11434`。
|
||||
- 所有 AI Agent、LLM 推理與 embedding 預設必須走 Ollama 三主機級聯:GCP-A `34.87.90.216:11434` → GCP-B `34.21.145.224:11434` → 111 `192.168.0.111:11434`。
|
||||
- `services/ollama_service.resolve_ollama_host()` 是主機解析契約;`OLLAMA_HOST`、`HERMES_URL`、`EMBEDDING_HOST`、`OLLAMA_API_BASE` 只接受 GCP-A / GCP-B / 111 或 110 的核准轉發端口。
|
||||
- Gemini 只能作為 Ollama 主路徑失敗後的備援,或 ADR-028 明確鎖定的 MCP Grounding、PPT/vision、週/月報、Code Review、EA HITL、複雜 SKU 升級等低頻場景。
|
||||
- 188 直連 GCP-A / GCP-B timeout 時,resolver 可先使用同順位 110 proxy rescue:GCP-A direct → `192.168.0.110:11435` → GCP-B direct → `192.168.0.110:11436` → 111。proxy rescue 只是同一順位的可用入口,不代表 GCP direct host 已恢復。
|
||||
- `OLLAMA_RESOLVE_HOST_HEALTH_SKIP_ENABLED=true` 時,resolver 會讀最近 `host_health_probes`;若 direct GCP-A/GCP-B 在視窗內已被判定不健康,會直接略過該 direct endpoint,先試同順位 proxy rescue,避免每 120 秒 cache refresh 都等待 direct timeout。此 skip 只套用 direct GCP,不套用 110 proxy。
|
||||
- `config.OLLAMA_HOST`、`config.HERMES_URL`、`config.EMBEDDING_HOST` 只保留為舊 caller 相容常數;import-time 不得 probe network,也不得因 GCP-A/GCP-B 短暫不可用而 freeze 到 111。需要即時路由時一律呼叫 `get_ollama_host()`、`get_hermes_url()`、`get_embedding_host()` 或 `OllamaService`。
|
||||
- Gemini 只能作為 Ollama 主路徑失敗後的備援;MCP Grounding、PPT/vision、週/月報、Code Review、EA HITL、複雜 SKU 升級等舊鎖定場景也必須先走 GCP-A → GCP-B → 111。
|
||||
- 188 `192.168.0.188` 僅是 App / DB / scheduler / Telegram bot 容器宿主與 AutoHeal target,不可作為 Ollama 節點。
|
||||
- 通用 AI 文案、關鍵字、商品洞察與 Telegram Q&A 第一響應不得 Gemini-first。
|
||||
- Hermes intent / analyst 路徑不得手刻 `/api/generate` 或只 resolve 單次 host;必須走 `OllamaService`,讓同一請求可依序 retry GCP-A → GCP-B → 111。
|
||||
- Hermes intent / analyst 路徑不得手刻 `/api/generate` 或只 resolve 單次 host;必須走 `OllamaService`。預設 `HERMES_ALLOW_111_FALLBACK=false`,同一請求只跑 GCP-A → GCP-B;兩台都失敗時回規則引擎或 DB 證據 fallback,不把批量價格分析轉嫁到 111。救急時才可顯式設 true 允許 111 接手。
|
||||
- NemoTron qwen3 dispatch 的 `/api/chat` tool-calling 路徑也必須同一請求最多嘗試三台 Ollama,第一台失敗要 `mark_unhealthy()` 後再試下一台,最後才 fallback NIM。
|
||||
- PPT vision、PPT 文案 final fallback、MCP 離線 final fallback 等特殊 Ollama 路徑也不得只打單一 host;如需 `/api/generate`,一律透過 `OllamaService.generate()`。
|
||||
- Code Review pipeline 也必須 Ollama-first:Hermes scan 與 OpenClaw assessment 都走 `OllamaService` 三主機 retry;Gemini telemetry 只能以 `code_review_openclaw_gemini` 出現,表示 Ollama/可選 Claude 備援都失敗後才啟用。
|
||||
- Code Review Hermes scan 預設不呼叫 LLM,改用 deterministic fast static scan,避免部署後先卡三段 Ollama timeout;需要 LLM 掃描時才以 `CODE_REVIEW_HERMES_LLM_SCAN_ENABLED=true` 啟用本地矩陣。
|
||||
- Code Review Hermes LLM scan 啟用時才使用本地模型矩陣,且預設只跑 GCP-A `qwen2.5-coder:7b` → GCP-B `gemma3:4b`;`CODE_REVIEW_ALLOW_111_FALLBACK=true` 時才允許落到 111,並由 `OllamaService` 降級到 `llama3.2:latest`。不啟用 Gemini 備援,本地掃描失敗時只回空 findings 並交由 OpenClaw 本地矩陣續跑。
|
||||
- Code Review OpenClaw assessment 預設只跑 GCP-A → GCP-B:GCP-A `qwen2.5-coder:7b`、GCP-B `gemma3:4b`;primary timeout 預設 `15s`、secondary timeout 預設 `60s`,讓 A 掛時快速讓位給 B,且 B 有足夠時間完成審查 prompt。111 是最後救急節點,但部署後重分析預設不打 111;只有 `CODE_REVIEW_ALLOW_111_FALLBACK=true` 才允許 111 接手,並降級到 `llama3.2:latest`。Code Review 的 Ollama `keep_alive` 預設為 `5m`,不得再用 `24h` 長駐 runner 壓住 GCP-B/111。GCP-A/GCP-B 都失敗且 Claude/Gemini 未顯式開啟時,必須回 deterministic 本地降級摘要,不呼叫 Gemini、不落 111、不走其他雲端模型。
|
||||
- Embedding / semantic RAG 背景任務預設只跑 GCP-A → GCP-B:`OpenClawLearningService` embedding worker 與 `RAGService` 查詢 embedding 呼叫 `OllamaService.generate_embedding(..., allow_111_fallback=False)`;111 只可作人工明確指定的救急路徑,不承接 `bge-m3` 背景批次。`OLLAMA_EMBED_TIMEOUT` / `OLLAMA_EMBED_MAX_TIMEOUT` 預設 `30s`、`OLLAMA_EMBED_KEEP_ALIVE=1m`、`OLLAMA_EMBED_MAX_CHARS=4000`;此上限依 GCP-B `bge-m3` 實測 6–23s 波動調整,避免慢但成功的 embedding 被 15s cap 誤殺。
|
||||
- `allow_111_fallback=False` 時,若 resolver 因 unhealthy cache 回傳 111,不得直接結束 embedding;必須強制改試尚未嘗試的 GCP-A / GCP-B,避免正式 log 出現 `tried=[]` 或只試單台 GCP-B。
|
||||
- `allow_111_fallback=False` 且 GCP-A / GCP-B 皆失敗時,背景 embedding 會開啟短暫 GCP failure circuit(預設 60 秒),期間不重複打兩台 GCP、不落 111,避免 worker 與 log 被連續失敗拖慢;GCP 恢復後會自然再試。
|
||||
- 背景 embedding 的 GCP-only 熔斷屬於可降級背景能力,應記錄為明確 WARNING 與 circuit 狀態,不應每次污染 ERROR 通道;真正允許三主機 fallback 的同步 embedding 全失敗仍保留 ERROR。
|
||||
- OpenClaw embedding worker 遇到 GCP-only failure circuit 時,必須把已 claim 任務退回 `pending` 並延後處理,不得扣 `attempts`、不得把同批任務刷成 `failed`;熔斷期間也不得繼續 claim 新任務。
|
||||
- Scheduler host health probe 不只看 `/api/tags`;GCP-A / GCP-B 節點必須再通過 `bge-m3` `/api/embed` 實作探針,才算 healthy。探針 timeout 預設 30s,111 預設不納入這個背景 embedding 探針,避免監測任務把 fallback Mac 載入 `bge-m3`。
|
||||
- 背景 embedding 會讀取最近 `host_health_probes` runtime 結果;若 GCP-A / GCP-B 在 `OLLAMA_EMBED_HOST_HEALTH_SKIP_WINDOW_MINUTES=20` 視窗內已被標為 unhealthy,`OllamaService.generate_embedding(..., allow_111_fallback=False)` 會先跳過該節點並開啟短暫 GCP circuit,不再等待 30 秒 timeout,也仍不落 111。此功能由 `OLLAMA_EMBED_HOST_HEALTH_SKIP_ENABLED=true` 控制,DB 讀取失敗時 fail-open 回到原本網路 retry。
|
||||
- BGE-M3 一致性檢查是監測任務,不是 fallback 壓測;預設只比對 GCP-A / GCP-B。111 Mac fallback 只有 `EMBED_CONSISTENCY_INCLUDE_111=true` 時才納入,避免每週背景檢查把 `bge-m3` 載入 111。
|
||||
- OpenClaw Telegram Q&A 主路徑也不得綁單一 host:`_call_qwen3_qa()` 必須透過 `OllamaService` 跑 GCP-A → GCP-B → 111,並把實際落點寫入 `ai_calls.provider`。
|
||||
- OpenClaw Telegram 圖片商品辨識也必須 Ollama-first:`_identify_product_name_with_ollama_vision()` 透過 `OllamaService` 嘗試 GCP-A → GCP-B → 111;Gemini 只允許以 `openclaw_bot_image_gemini` caller 作為失敗後備援。
|
||||
- OpenClaw 週報、月報、Meta analysis、日報洞察、Telegram PPT 分析與 MCP fallback 也必須 Ollama-first;Gemini caller 只能帶 `_gemini_fallback` 或明確 fallback caller 語意,且不得先於 Ollama/NIM 被呼叫。OpenClaw strategy 的 Ollama `keep_alive` 預設為 `5m`,避免報告型任務把 GCP-B/111 runner 長駐 24h。
|
||||
- OpenClaw 週報、月報、Meta analysis、日報洞察與每日報告的 Gemini/NIM 備援 caller 必須登錄在 caller registry、AI 觀測台 agent group 與 Telegram 狀態統計,避免 fallback 用量被歸類為未知或漏算。
|
||||
- `ai_calls.provider='ollama_other'` 只允許作為 unresolved/unknown Ollama telemetry bucket,例如全 host 失敗、尚未選定實際 GCP-A/GCP-B/111 host 或舊 caller 未帶 host;不得把 `ollama_other` 當成實際路由目標或新增非核准 Ollama host。
|
||||
- GCP-B 若缺 caller 指定的 coder/large 模型,`OllamaService` 必須先在 GCP-B 改用 `OLLAMA_SECONDARY_MODEL_FALLBACK`(預設 `gemma3:4b`),不可因 model 404 把整台 GCP-B 標成 unhealthy 後直接推到 111;真正 timeout / HTTP 5xx 才標 host unhealthy。
|
||||
- Gemini API 出站有第二道 kill switch:`GEMINI_FALLBACK_ENABLED` 預設為 `false`。即使 `GEMINI_API_KEY` 存在,通用 AI fallback、OpenClaw 報告/QA/PPT/圖片、MCP Grounding 與 Code Review L3 都不得呼叫 Gemini;只有操作員明確設為 `true` 時,Gemini 才能作緊急備援。
|
||||
- `docker-compose.yml` 的 `momo-app`、`scheduler`、`telegram-bot` 必須明確設定 `GEMINI_API_HARD_DISABLED=${GEMINI_API_HARD_DISABLED:-true}` 與 `GEMINI_FALLBACK_ENABLED=${GEMINI_FALLBACK_ENABLED:-false}`;`.env` 可保留 `GEMINI_API_KEY`,但不得因 key 存在就讓核心容器產生 Gemini 付費出站。
|
||||
- OpenClaw 日/週/月/Meta 等敘事報告屬長任務,Ollama 只能走 GCP-A → GCP-B;不得使用 111 final fallback 承接長文生成。GCP 兩台都不可用時,應走既有 Gemini hard-disabled guard 後的 NIM / deterministic degraded path,避免 111 被非即時分析壓高負載。
|
||||
- Gemini 不可被任何狀態面板或 router 推薦為主提供者:`AIProviderService._get_recommended_provider()` 不得回傳 `gemini`,只能顯示為 fallback 狀態;`llm_model_router` 的 `ea_engine` 若收到 `gemini-*` default 必須改回 `hermes3:latest`,需要深推理時才升本地 `deepseek-r1:14b`。
|
||||
- ElephantAlpha prompt / agent registry 不得再把 OpenClaw 描述為 Gemini 主模型;OpenClaw 是 `qwen2.5-coder:7b` / `qwen3:14b` Ollama-first 策略師,Gemini 僅能在 guard 顯式解鎖後作 emergency fallback。
|
||||
- 111 `192.168.0.111` 只是最後一道 Mac fallback,不承接 7B+、vision、long-context 模型長駐;`OllamaService.generate()` 落到 111 時會將 `qwen3`、`deepseek-r1`、`hermes3`、`qwen2.5*`、`gemma3`、`llava`、`minicpm-v` 與 7B+ 模型依 `OLLAMA_111_MODEL_DOWNGRADE_PATTERNS` 降級到 `OLLAMA_111_MODEL_FALLBACK=llama3.2:latest`,並以 `OLLAMA_111_KEEP_ALIVE=5m`、`OLLAMA_111_MAX_TIMEOUT=20`、`OLLAMA_111_NUM_CTX=4096`、`OLLAMA_111_NUM_PREDICT=512` 封頂。OpenClaw 報告型路徑的業務 keep-alive 預設 `5m`;Code Review 以 `CODE_REVIEW_ALLOW_111_FALLBACK=false`、Hermes 以 `HERMES_ALLOW_111_FALLBACK=false` 預設跳過 111,避免 16GB RAM 主機與 GCP-B 被長駐 runner、長輸出與 24h keep-alive 壓到高 load。
|
||||
- Scheduler 每 15 分鐘執行 `run_ollama_111_usage_guard_check()`,只讀 `ai_calls` 統計最近視窗的 GCP-A / GCP-B / 111 呼叫量;預設 60 分鐘內 Ollama 呼叫至少 20 次、111 至少 3 次且占比 >= 5% 才推 Telegram。這是觀測護欄,不改路由、不寫 DB、不自動重啟服務。
|
||||
- `OllamaService` 對 111 final fallback 有 circuit breaker:預設最近 60 分鐘 Ollama 呼叫至少 20 次、111 至少 5 次且占比 >= 5% 時,短暫跳過 111(`OLLAMA_111_CIRCUIT_CACHE_SEC=60`),避免 111 在已偏高時繼續承接長任務;DB 觀測失敗時 fail-open,不讓主要 GCP-A/GCP-B 路由被觀測層中斷。
|
||||
- 111 的 LAN 入口必須經 `scripts/ops/ollama111_allow_proxy.py` allowlist proxy:真實 Ollama 綁 `127.0.0.1:11434`,proxy 綁 `192.168.0.111:11434`,預設只允許 111 本機與 188 生產宿主;110 / 121 / 其他 LAN client 不能直接打 111,避免跨專案 CI 或 VM 繞過 momo-pro router 載入 7B+ runner。111 上以 `scripts/ops/install_ollama111_allow_proxy.sh` 安裝 user LaunchAgent,安裝器會把 proxy script 複製到 `~/.local/share/momo-pro-system/ollama111_allow_proxy.py`,讓 LaunchAgent 不依賴 iCloud repo 掛載路徑,並讓 proxy 與 `OLLAMA_HOST=127.0.0.1:11434` 在登入/重啟後自動恢復。拒絕日誌以 `OLLAMA111_PROXY_REJECT_LOG_DEDUP_SEC=60` 去重,避免 121 這類旁路探測刷爆 111 磁碟日誌。
|
||||
- ElephantAlpha 的 `price_drop_alert` / `market_opportunity` Telegram HITL 告警必須把同款證據獨立呈現,至少包含 `match_type`、`price_basis`、`alert_tier` 與 `match_score`;沒有高信心同款與總價可比證據時,不得把 PChome/MOMO 價差寫成可直接跟價建議。
|
||||
|
||||
## 零之零、產品定位正名(2026-06-15)
|
||||
|
||||
- 本專案的營運定位正名為「PChome 業績成長自動化作戰系統」。
|
||||
- 主要目標是提升 PChome 銷售業績;MOMO 是目前已接入的外部價格參考來源,不再把 PChome 視為附屬競品語意。
|
||||
- 使用者可見 UI、Telegram 與報表文案必須白話、可行動,優先使用「商品對應」「可直接比價」「待補對應」「放大價格優勢」「檢查售價與活動」等營運語言,避免把 `identity_v2`、`match_score`、`candidate queue` 等工程詞直接丟給使用者。
|
||||
- `services/pchome_revenue_growth_service.py` 是第一版只讀作戰清單:讀 PChome 後台業績與已驗證 MOMO 外部價格參考,輸出 `/api/ai/pchome-growth/opportunities`。此服務不呼叫 LLM、不抓外站、不寫 DB。
|
||||
- 2026-06-15 只讀盤點確認:`daily_sales_snapshot."商品ID"` 與 `competitor_prices.competitor_product_id` 在正式資料中直接重疊為 0。因此第一版作戰清單不得硬接兩邊 ID;若沒有可驗證對應,只能輸出「先補商品對應」任務。
|
||||
- 蝦皮與酷澎暫停接入,不進作戰清單、不發告警;後續只可透過 official API / provider API / manual CSV 進 `external_offers` 類正規化層,並清楚標示資料品質。
|
||||
- V10.607 新增 `external_market_sources` / `external_offers` 正規化層與 `/api/ai/pchome-growth/source-contract` 只讀 API。MOMO 先以既有比價快取橋接進來源狀態;蝦皮與酷澎只保留 official API、provider API、manual CSV contract,預設暫停且不進告警。
|
||||
- V10.608 新增 `/api/ai/pchome-growth/external-offers/csv-dry-run` 與 AI 情報頁「外部報價預檢」。CSV 預檢只讀、不寫 DB;逐列回報「可使用」「需人工確認」「不能使用」,並支援中文表頭,避免格式小錯造成整批匯入失敗。
|
||||
- V10.609 明確把外部報價主路徑改為自動化:`run_external_offer_sync_task` 每 4 小時將已確認同款的既有比價快取同步進 `external_offers`。CSV 只保留為 API / crawler / provider 失敗時的備援預檢入口,不是日常營運主流程。
|
||||
- V10.610 起 `/api/ai/pchome-growth/opportunities` 優先讀取 `external_offers` 的自動同步資料;只有新資料層缺資料時才 fallback 舊 `competitor_prices`。API stats 會回傳資料來源計數,方便確認作戰清單是否已走新資料層。
|
||||
- V10.611 起 `/ai_intelligence` 是營運使用者主入口;V10.617 已將舊「今日作戰入口 / 外部報價預檢」改為「今日重點總覽 / 備援資料檢查」,主流程不得再把人工 CSV 放在前段。
|
||||
- V10.612 起 `/api/ai/icaim/dashboard` 的「MOMO 外部價格參考」表格也優先讀 `external_offers`,缺資料才 fallback `competitor_prices`;價差與風險改採 PChome 視角,正數代表 PChome 比 MOMO 外部參考價高。
|
||||
- V10.613 起高可見前台頁面必須以繁體中文呈現:程式碼審查、AI 自動化健康檢查、PPT 產線與商品看板操作標籤不得使用英文工程標題或簡體字;測試需防止頁面文案退回英文。
|
||||
- V10.614 起部署監控、基礎設施生命線與 PPT 產線狀態也納入繁中守門:前台不得顯示 `Dashboard`、`Pipeline`、`Runtime` 等工程詞,動態階段需轉成「測試 / 建置 / 部署」。
|
||||
- V10.615 起 AI 智慧推薦頁必須把 Ollama 顯示為「Ollama 主路徑」,Gemini 只能顯示為「Gemini 備援」且手動選項停用;使用者可見錯誤與搜尋流程不得出現 `Web Search`、`Token:`、半形英文冒號等工程文案。
|
||||
- V10.616 起主商品看板 `/` 的統計與補強區塊也納入繁中守門:不得顯示 `ACTIVE`、`PICK COUNT`、`AVG CONFIDENCE`、`EVIDENCE GAP`、`PCHOME MATCH BACKFILL` 等工程標籤;畫面需使用「有效商品」「挑品數」「平均信心」「待補證據」「PChome 比價補強」等白話營運文案。
|
||||
- V10.617 起 `/ai_intelligence` 必須採「先給下一步」的作戰導向 UI:首屏需先回答「今天先做什麼」,再呈現商品處理進度、外部價格來源與操作捷徑;今日處理清單需用表格呈現優先級、建議動作、商品、近 7 天業績、比價結果、資料可信度與下一步;MOMO 外部價格參考需顯示價格風險分佈,且表格需以 PChome 價格優先,明確顯示「PChome 貴 / PChome 便宜」與可信度,不得只用大段文字說明使用方式。
|
||||
- V10.618 起 `/price_comparison` 也必須採「先給下一步」的比價決策 UI:首屏需顯示目前卡在哪一步、PChome / MOMO 資料準備狀態與下一個按鈕;比價結果需先呈現「需檢查價格 / 可主推曝光 / 價格接近」分佈,再用表格列出每筆商品的下一步,不得只呈現 Step 流程或原始價差表。
|
||||
- V10.619 起 MOMO 比價候選來源新增「PChome 商品導向搜尋」:當比價 API 已有 PChome 商品但缺 MOMO 清單時,必須用每筆 PChome 商品名稱產生精準搜尋詞反查 MOMO,保留品牌、品名、容量與組合線索;新版 MOMO 搜尋頁需解析 Next.js `goodsInfoList` payload。此路徑只擴大候選池,不放寬同款 matcher 門檻。
|
||||
- V10.620 起 `unit_comparable` 不再一律丟人工確認:若 `build_unit_price_comparison()` 可產生明確容量/數量、MOMO 單位價、PChome 單位價與差距百分比,候選需標為「自動單位價比較」並回傳 `auto_compare_type=unit_price`。此類候選可自動呈現價格壓力,但不得混入舊總價同款比價表,也不得直接寫入正式價差或自動改價;無法產生單位證據時才維持「需人工確認」。
|
||||
- V10.621 起 `/price_comparison` 的「自動找 MOMO 候選」會把可直接總價比價與自動單位價候選同步到 `external_offers`,`ingestion_method='targeted_momo_search'`,人工確認候選不得寫入。`external_offers.raw_payload_json.price_basis='unit_price'` 時,作戰清單必須使用 `unit_price_comparison` 的 MOMO / PChome 單位價與 `unit_gap_pct` 判斷價格壓力;不得把 MOMO 組合總價與 PChome 單品總價直接相減。此同步只影響外部價格參考與作戰清單,不寫 `competitor_prices`,也不自動改價。
|
||||
- V10.622 起任何 `external_offers` 自動同步成功寫入後,必須呼叫 `mark_pchome_growth_cache_stale()` 寫入共享 cache epoch;`/api/ai/pchome-growth/opportunities` 讀快取前必須比對 `get_pchome_growth_cache_epoch()`。這是跨 Gunicorn worker 的可見性保護,避免自動候選已進外部價格參考,但 AI 情報頁仍回 120 秒舊作戰清單。
|
||||
- V10.623 起 `/price_comparison` 與 `/ai_intelligence` 不得只靠大段文字說明流程:比價頁第一屏必須有主 KPI、目前卡點、四步流程與結果決策摘要;作戰頁第一屏必須有今日任務、可立即處理、待補比價與最新業績日。所有狀態都要由實際 API/前端狀態驅動,讓使用者一眼知道下一步要按哪個動作。
|
||||
- V10.638 起 PChome 導向 MOMO 補抓會把「找到但不能自動比價」的候選以 `match_status='needs_review'`、`data_quality_status='needs_review'` 保存到 `external_offers`;這些候選不得進價格壓力判斷,也不得發告警,但 `/api/ai/pchome-growth/opportunities` 可回傳待確認候選數,讓 UI 顯示「已有候選待確認」而不是只顯示無法比價。
|
||||
- V10.639 起待確認候選排序必須容忍缺少單位數量;沒有 `momo_total_quantity` / `competitor_total_quantity` 時仍可保存為 `needs_review`,不得中斷 PChome 導向 MOMO 回填。
|
||||
|
||||
## 零之一、12 Agent 決策信封(2026-05-24)
|
||||
|
||||
- 12 角色分工不作為 12 個常駐模型;在產品層統一收斂成 `decision_envelope`,由 Hermes / NemoTron / OpenClaw / ElephantAlpha 與人工審核、PPT QA、競品 review queue 共用。
|
||||
- `decision_envelope` 必須至少能表達:`decision_type`、`severity`、`evidence[]`、`recommended_action`、`expected_impact`、`confidence`、`guardrails`、`trace`。
|
||||
- `guardrails.can_auto_execute=false` 是預設;價格調整、正式比價覆寫、PPT 發送與修復執行都必須遵守 HITL 或既有 service guard,不得因 Agent 信心高就繞過 matcher / feeder / review service。
|
||||
- 證據不足時不得輸出空泛效益預測;必須標記 `data_quality=missing|partial|stale`,並把建議行動降級成 `human_review`、`needs_research` 或 `silence_alert`。
|
||||
- Telegram `triaged_alert()` 已支援渲染 `decision_envelope`,讓告警固定呈現嚴重度、證據、建議行動、預期影響、信心度與追蹤 ID;後續觀測台與 PPT 也應共用同一份欄位語意。
|
||||
- NemoTron `price_alert` / `human_review` 派發會把同款證據、價差、七日銷量變化、營收流失、HITL 邊界與資料品質寫入同一份 `decision_envelope`,並同步放入 EventRouter event 與 KM metadata;12 Agent 後續只能沿用此信封補充分析,不得繞過 matcher / feeder / review service 直接改價或覆寫比價資料。
|
||||
- EventRouter / Telegram 的 HITL callback 必須優先使用 `decision_envelope.decision_id` 作為事件追蹤 ID;若上游未帶 `event.id`,`triaged_alert()` 仍會用 `decision_id` 產生 `momo:eig:*` callback,避免價格決策審核落成 `unknown`。所有 `momo:eig:*` callback 必須以 UTF-8 byte-safe 截斷,確保 `callback_data` 不超過 Telegram 64-byte 限制。
|
||||
- 競品比價相關的 Agent 建議只能讀 `competitor_match_attempts` / review queue / `competitor_prices` 的既有證據;不得直接寫 `competitor_prices` 或覆蓋 `_should_upsert_competitor_price()` 的保護規則。
|
||||
- 已帶 `decision_envelope` 的價格/覆核事件必須由 EventRouter 直接渲染證據模板,不再進 L1/L2 AI 重新摘要;Telegram 決策信封需顯示標的 SKU、商品名稱、PChome 候選、evidence、guardrails 與 HITL 動作,避免已有實證的比價告警被二次生成文字稀釋或造成額外模型成本。
|
||||
- PChome 覆核隊列本身也必須輸出 `decision_envelope`:`fetch_competitor_review_queue()`、`fetch_competitor_review_queue_page()` 與 `/api/pchome-review/queue` 的每筆候選需帶相同的 `subject`、`evidence`、`recommended_action`、`expected_impact` 與 `guardrails`,供 Dashboard、Agent、Telegram 與 PPT 共用;任何下游不得另寫一套比價狀態翻譯或繞過 HITL guardrails。
|
||||
- Dashboard 覆核卡與 `/api/export/excel/pchome-review` 也必須顯示/匯出 `decision_envelope` 的等級、資料品質、建議代碼、HITL、trace 與 `can_auto_execute=false` 邊界;操作員離開系統畫面或下載 Excel 後,仍要看得到「不可自動寫正式價差」的 guardrails。
|
||||
- OpenClaw 週報/日報/月報與 competitor PPT 不得再各自重算或翻譯 PChome 覆核狀態;必須透過 `competitor_intel_repository.summarize_review_decision_envelopes()` 讀取同一份 `decision_envelope` 摘要,並在 prompt / data_summary / KPI slide 保留 HITL 與 `can_auto_execute=false` 邊界。
|
||||
- Webcrumbs / Shared UI host data 也必須透過 `summarize_review_decision_envelopes()` 輸出 `reviewDecisionBrief`,並在 metadata 保留 review queue、HITL、auto-execute-blocked、`decision_support_rate`、`catalog_comparable_count` 與 catalog review lane counts;不得另寫一套 PChome 覆核摘要或在前端 runtime 重新推論價格行動。
|
||||
- ElephantAlpha 的 `resource_optimization` 與低信心 `ea_escalation` 也必須輸出 `decision_envelope`:資源壓力信封只能使用 `action_plans`、CPU 實測、hygiene 結果與 insight/action trace,不得加入 LLM 預測效益;`triaged_alert()` 對 `ea_escalation` 亦需渲染信封並以 `decision_id` 作為 callback 追蹤 ID。
|
||||
|
||||
## 一、四 AI Agent 路由架構
|
||||
|
||||
@@ -29,7 +103,7 @@ SQL漏斗(~300筆)
|
||||
任務: 競價威脅分類 → TOP 20 HIGH/MED/LOW
|
||||
↓
|
||||
[NemoTron / qwen3] — 派發器
|
||||
主路徑: qwen3:14b @ Ollama 三主機級聯
|
||||
主路徑: qwen3:14b @ GCP-A/GCP-B;落到 111 時自動降級 llama3.2
|
||||
備援: NVIDIA NIM meta/llama-3.1-8b-instruct
|
||||
任務: Tool Calling → Telegram 告警 / DB 寫入
|
||||
↓
|
||||
@@ -50,14 +124,34 @@ SQL漏斗(~300筆)
|
||||
- 後台入口:`POST /api/ai/product-picks/generate`,`/ai_intelligence` 可手動產生清單。
|
||||
- 配對來源仍以 PChome crawler 真實搜尋結果為準;無競品資料時不生成挑品。
|
||||
- 比對覆蓋率補強入口:`POST /api/ai/pchome-match/backfill`,優先補抓仍無有效 PChome 配對的高價 ACTIVE 商品,完成後自動重算 AI 挑品清單。
|
||||
- 過期價格刷新入口:`POST /api/ai/pchome-match/refresh-stale`,只針對已建立 `identity_v2` 但 `expires_at` 過期的 PChome product_id 執行 `run_expired_identity_refresh()`;不得跑 fresh search recovery,不得呼叫 LLM,完成後重算 AI 挑品並清除 Dashboard / competitor intel cache。
|
||||
- 過期 identity 搜尋救援入口:`POST /api/ai/pchome-match/recover-stale` 預設必須關閉主操作入口,僅保留只讀 preview;正式 smoke 顯示小批次成功率不足且耗時偏高時,不得在 Dashboard 顯示日常操作按鈕。若需操作員手動執行,必須先明確設定 `PCHOME_STALE_RECOVERY_ENABLED=true`,再對已過期 `identity_v2` 先走既有 PChome product_id refresh;只有舊 ID 查無商品或重評低於門檻時,才允許受控 fresh search recovery。救援隊列必須先排除 variant、catalog、commercial condition、count、bundle、unit-price 與任選 / 多款 / 香味 / 色號 / 即期 / 融燭燈 / 香氛蠟燭 / `+` / `xN` / `*N` / 具名香味或膚感版本等高風險名稱訊號。這條路徑可抓 PChome,但不得呼叫 LLM;正式寫入仍必須通過 matcher、hard veto、auto price write safety 與 overwrite protection。
|
||||
- 補抓狀態入口:`GET /api/ai/pchome-match/backfill/status` 除背景任務狀態外,必須回傳 read-only coverage snapshot:`active_with_price` / `valid_matches` / `match_rate` / `fresh_matches` / `fresh_match_rate` / `decision_ready_matches` / `decision_ready_rate` / `stale_matches` / `pending` / `actionable_review_count`,供 Dashboard 顯示目前該刷新過期價格或補抓未搜尋商品;此端點不寫 DB、不呼叫 LLM、不抓外站。`match_rate` 是身份覆蓋率,`fresh_match_rate` 是已配對 identity 內的新鮮比例,`decision_ready_rate` 才是可直接進入決策、圖表與簡報的 ACTIVE 商品比價覆蓋率。
|
||||
- 排程閉環:`run_pchome_match_backfill_task` 每日 10:30 執行,補抓 PChome 待比對商品、寫入歷史價格,再重算 `strategy='product_pick'` 清單。
|
||||
- 商品看板第一屏:`/` 的 V2 看板直接以 `products`、`price_records`、`competitor_prices`、`ai_price_recommendations` 顯示比對覆蓋率、PChome 優勢、MOMO 威脅、AI 挑品與待比對優先清單;`filter=ai_picks` 可查看 50 品 AI 挑品列表,並在列表上方顯示平均信心、平均價差、最大價差與估算總價差空間,列表列內顯示 AI 排名與建議理由,且可透過 `/api/export/excel/ai-picks` 匯出 50 品 Excel 操作清單。商品看板深度快取同時寫入 `data/dashboard_full_cache.pkl`,供多個 Gunicorn worker 共用,避免部署後各 worker 重複重建 7,000+ 商品統計造成開頁變慢;所有資料異動與 AI 挑品重算都透過 `clear_dashboard_cache()` 同步清除記憶體與共享快取,手動重算 API 會立即預熱商品看板快取,避免第一位使用者承擔重建成本。
|
||||
- PChome / MOMO 競價摘要出口 `services/competitor_intel_repository.py` 使用 30 分鐘共享快取(`COMPETITOR_INTEL_CACHE_TTL_SECONDS` 可調),避免 `/growth_analysis`、`/daily_sales`、PPT/AI 報表每次請求重跑昂貴覆蓋率與價差趨勢查詢;`run_competitor_price_feeder_task` 與 PChome backfill 完成後會主動清除快取。快取只包摘要輸出,不改 matcher 的高信心門檻與 identity_v2 準確性規則。
|
||||
- 商品看板第一屏:`/` 的 V2 看板直接以 `products`、`price_records`、`competitor_prices`、`competitor_match_attempts`、`competitor_match_reviews`、`ai_price_recommendations` 顯示比對覆蓋率、PChome 優勢、MOMO 威脅、AI 挑品、待比對優先清單與 PChome 覆核隊列;`filter=ai_picks` 可查看 50 品 AI 挑品列表,`filter=pchome_review` 可直接查看需人工處理的比價覆核 SKU,並以 DB 分頁支援 search/category/status 後的完整隊列,不得只截前 50 筆。覆核狀態篩選必須至少包含全部、需單位價、已排除、低信心、價格過期、找不到同款與人工閉環,讓人工可依 matcher 診斷類型分批處理。列內顯示候選 PChome 商品、候選價、match score、單位價換算摘要、人工動作與 matcher 診斷原因標籤(品牌不符、商品線不符、容量差異、組合差異、需單位價、價差極端等),不得只顯示籠統「待比對」。`/api/export/excel/pchome-review` 必須匯出同一套覆核隊列、人工處置、候選 PChome、單位價比較與原始診斷,讓人工覆核、簡報與後續 AI 分析共用同一份證據。`/api/pchome-review/<sku>/decision` 是人工閉環入口:`accept_identity` 才可把候選寫入 `competitor_prices` 與 `competitor_price_history` 並打上 `manual_review/manual_accept/identity_v2`;`reject_identity`、`unit_price_required` 與 `needs_research` 只寫 `competitor_match_reviews` 並追加 manual attempt,不得把不同販售組合或否決候選灌入正式價差。PChome feeder 後續搜尋同一候選時必須讀取 `competitor_match_reviews`:已否決候選寫 `manual_rejected` 並跳過正式寫入,且必須繼續評估下一個候選,不能讓已否決候選長期阻塞同 SKU;已標記單位價候選寫 `manual_unit_price_required`;已要求補搜尋候選寫 `manual_needs_research` 並停留在覆核隊列;已採用候選可保守補到最低門檻並保留 `manual_review/manual_accept` 標籤。搜尋候選池只有強同款分數達 `0.90` 才可提前停止,避免 0.76 灰區候選卡掉後續更精準搜尋詞。人工 `reject_identity`、`unit_price_required`、`needs_research` 若命中當前正式候選,必須將同候選 `competitor_prices` 過期,不得繼續顯示正式總價差。商品列表必須將 `manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 顯示為明確人工閉環狀態,不可回落成籠統「待比對」。`fetch_competitor_coverage()` 必須輸出人工採用、人工否決、人工單位價與採用率,daily/growth/PPT 共用 payload 必須顯示人工閉環成效,避免只呈現待審數。商品看板深度快取同時寫入 `data/dashboard_full_cache.pkl`,供多個 Gunicorn worker 共用,避免部署後各 worker 重複重建 7,000+ 商品統計造成開頁變慢;所有資料異動與 AI 挑品重算都透過 `clear_dashboard_cache()` 同步清除記憶體與共享快取,手動重算 API 會立即預熱商品看板快取,避免第一位使用者承擔重建成本。
|
||||
- PChome re-score 回收線:`rescore_accepted_current` 只能表示最新版 matcher 判定「值得人工覆核身份」,不可直接寫入正式 `competitor_prices`;`no_match`、`price_basis=none`、`alert_tier=suppress`、`variant_selection_review` 不得進入此隊列。`fetch_competitor_coverage()` 必須輸出 `rescore_accepted_count`,Dashboard、daily/growth 與 OpenClaw 競品摘要都要把「重算待人工覆核」獨立呈現,避免和一般低信心/單位價覆核混在一起。
|
||||
- PChome 低信心操作分流:Dashboard 與 read-only `/api/pchome-review/queue` 必須把近門檻可救、證據不足、低信心舊候選拆成 `recoverable_low_score`、`true_low_confidence`、`legacy_low_score` 三個可篩選桶;廣義 `low_score` 僅作 repository/export 相容查詢,不可在 UI 中冒充單一操作分流。
|
||||
- PChome coverage 的 `attempt_status` / `rescore_accepted_count` / `actionable_review_count` 口徑必須與 review queue 對齊:統計「沒有新鮮有效 identity」的商品,而不是只統計「完全沒有 identity」的商品;已過期但可重算採用的 stale identity 仍應出現在待審數字中,避免 API 與 Dashboard 漏報。
|
||||
- `run_retryable_candidate_revalidation()` 的自動回刷主戰場仍限 `low_score` / `refresh_low_score` / `recoverable_low_score`;`true_low_confidence` 只有在已補 focused exact 規則的窄範圍品線、舊分數 >= 0.95、`comparison_mode='exact_identity'`、含 `strong_exact_spec_match` 且不含 commercial / variant / count / bundle / refill 等阻擋理由時,才可進入重評,不得全面打開人工審核池。`rescore_accepted_current` 只允許命中具名 focused exact 品線、舊分數 >= 0.76、且仍無 hard veto / 阻擋理由時進窄門回刷;最後仍由最新版 matcher 判定是否可寫正式價差,像不同指甲油型號 / 色號必須 hard veto。
|
||||
- 高分 `true_low_confidence` 的自動救回只能用具名 focused exact 線逐批擴充;同品牌、同品線、同規格/同組合的花美水 Relax、St.Clare 私密呼呼、BIOPEUTIC 果酸、台塑生醫嬰兒沐浴洗髮、Elizabeth Arden 八小時護唇膏與理膚寶水全面修復潤唇膏可走 total-price,色號、香味、款式、即期品與 catalog selection 仍維持 review / veto。
|
||||
- `true_low_confidence` focused exact 線必須同步接入 `run_retryable_candidate_revalidation()` 的 SQL 窄門,讓舊候選可被批次回收;該窄門只允許具名品線豁免 `variant_selection_review`,其他 hard veto / 型別、款式、香味、件數、組合、refill、commercial condition 阻擋仍不得回刷。
|
||||
- 任選 catalog focused exact 只允許雙方都明確是同品線任選賣場且規格一致的窄範圍案例,例如 FLORTTE 眼線液筆 0.5ml、露得清護手霜 56g 無香/有香、Kanebo ALLIE 持采亮化 UV 防曬水凝乳 60g;若有 `commercial_condition_gap`(即期品、短效、航空版等狀態差異),focused bypass 不得移除 `variant_selection_review`,不得自動寫正式價差。
|
||||
- O.P.I 指彩救回只允許同品牌、同 `類光繚` / `如膠似漆` 指甲油或指彩線,且共享 `ISL...` 精準型號 token 的案例自動走 total-price;不同型號/色號仍維持人工或 veto。此規則可接入 `true_low_confidence` revalidation 窄門,但不得變成「同品線即通過」。
|
||||
- 其他正式覆核池 focused exact 線只能針對「已在正式頁面反覆出現且有硬規格」的窄範圍族群,例如 The Ordinary 咖啡因 EGCG、Natures Care 綿羊油同入數、TOMOON 指甲剪同尺寸、HH 雙 200ml 組、SEBAMED 200ml x2、YES 9cm 剪刀;同尺寸、同入數、同組合或單側漏規格必須可由 matcher 明確判斷,不能只因同品牌同品線通過。
|
||||
- `/api/ai/pchome-match/backfill/status` 必須把近門檻重評池與過期 identity 救援池以只讀 `revalidation_preview` / `stale_recovery_preview` 曝光給操作員;預覽只復用正式候選 SQL 並受 limit / 60 秒快取限制,不啟動 PChome 搜尋、不呼叫 LLM、不寫 `competitor_match_attempts` / `competitor_prices`。重評 preview 必須先從最新 `competitor_match_attempts` 縮小候選,再用 `JOIN LATERAL` 取單一最新 MOMO 價;救援 preview 必須從過期 `competitor_prices` 小集合出發並用 `JOIN LATERAL` 取最新 MOMO 價,兩者都不得掃全量 `price_records`;Dashboard 只能顯示「可救援」觀測值,不得在未開啟 `PCHOME_STALE_RECOVERY_ENABLED` 時提供 recover-stale 執行按鈕;其中 `review_gated_count` 僅代表窄門 `true_low_confidence` exact 候選,不得被解讀為全量人工池可自動回刷。
|
||||
- PChome re-score audit 預設必須先取每個 SKU 的最新 `competitor_match_attempts` 狀態,再套用 status / reason 篩選;舊低信心歷史候選只能透過 `--include-historical-candidates` 明確進入考古掃描,避免已入隊、已否決或已修正 SKU 被舊紀錄重新推回報表。
|
||||
- production re-score `--apply-accepted` 僅可追加 `rescore_accepted_current` attempt 給人工覆核;執行後需清除 Dashboard / competitor intel cache,且必須抽查 `competitor_prices` / `competitor_price_history` 未新增正式價差。
|
||||
- production re-score 若曾把 `variant_selection_review` 追加成 `rescore_accepted_current`,必須用 `audit_competitor_match_attempt_rescore.py --retract-variant-accepted` 追加最新 `true_low_confidence` 退回列;此路徑只寫 `competitor_match_attempts`,不得刪歷史紀錄,也不得寫 `competitor_prices` / `competitor_price_history`。
|
||||
- PChome matcher replay 必須先守住假陽性:`EX8` 等型號不可被誤解析成 `x8` 入數;香氛固體凝膠 / 空氣芳香劑若一側為泛稱、一側含明確香味或 No. 款式,必須走 `aroma_scent_variant_conflict` veto,不得因同品牌同重量直接寫正式價差。
|
||||
- PChome matcher 對「同規格同數量」的多件組可以安全回收,但必須同時滿足:商品型別完全對齊、品牌同線、規格與數量對齊、沒有 variant / count / bundle / commercial / unit-price / price-ratio 阻擋理由,才可打 `safe_multi_component_exact_total_price` 並進 `exact / total_price / price_alert_exact`;混合組、香味款、色號款、catalog 任選仍需留在 `identity_review` 或 veto。護唇品 focused total-price 僅允許已明確建規則的 DHC 純欖 1.5g、FRUDIA 蜂蜜藍莓 10g、SEBAMED 嬰兒護唇膏 4.8g x2、理膚寶水滋養修護潤唇膏 4.7ml,不得把所有 lip/cosmetic catalog 一次放行。
|
||||
- PChome feeder 正式寫入必須再套一層價格資料閘門:只有 `match_type='exact'`、`price_basis='total_price'`、`alert_tier='price_alert_exact'` 且無 `variant_selection_review` 的結果可以自動寫入 `competitor_prices`;`manual_review` / `identity_review` 只能留在覆核隊列或人工採用流程,不得由 retryable replay 或 known identity refresh 自動升成正式價差。Rescore audit 若遇到 `variant_selection_review`,也不得產生 `accepted_current`。
|
||||
|
||||
| 角色 | 模型 | 主機 | 成本 | 每日限額 |
|
||||
|------|------|------|------|---------|
|
||||
| Hermes 分析師 | hermes3:latest / bge-m3 | GCP-A → GCP-B → 111 Ollama | 零 | 無限 |
|
||||
| NemoTron 派發器 | qwen3:14b;NIM fallback | GCP-A → GCP-B → 111;NVIDIA NIM 備援 | Ollama 零;NIM 配額內免費 | NIM 80 |
|
||||
| OpenClaw 策略師 | qwen3:14b / Gemini 鎖定場景 | Ollama-first;Gemini 備援 | Ollama 零;Gemini 需控管 | — |
|
||||
| NemoTron 派發器 | qwen3:14b;111 fallback 降級 llama3.2;NIM fallback | GCP-A → GCP-B → 111;NVIDIA NIM 備援 | Ollama 零;NIM 配額內免費 | NIM 80 |
|
||||
| OpenClaw 策略師 | qwen2.5-coder:7b / qwen3:14b;111 fallback 降級 llama3.2 | Ollama-first;Gemini emergency fallback only | Ollama 零;Gemini 預設封鎖 | — |
|
||||
| ElephantAlpha 編排者 | ElephantAlpha | 依部署環境 | 受控 | HITL / 任務制 |
|
||||
|
||||
---
|
||||
@@ -91,11 +185,11 @@ SQL漏斗(~300筆)
|
||||
- `/metrics` 匯出 `momo_ai_event_router_replay_total`。
|
||||
- `/metrics` 匯出 `momo_ai_autoheal_action_total` 與 `momo_ai_autoheal_duration_ms_count/sum/max`。
|
||||
- `/metrics` 在尚無事件時仍輸出 `momo_ai_*` zero-baseline series,讓 Prometheus/Grafana 重啟後可立即看到 metric names。
|
||||
- `/ai_automation_smoke` 提供登入後 smoke dashboard。
|
||||
- `/ai_automation_smoke` 提供登入後 AI 自動化健康檢查頁。
|
||||
- `/api/ai-automation/smoke` 提供 read-only JSON 狀態,不做外部網路呼叫。
|
||||
- Smoke API 會將最近快檢結果保存到 JSONL,dashboard 顯示最近狀態趨勢。
|
||||
- Smoke history 支援 JSONL 匯出、清理與每日 OK / Warning / Critical 摘要。
|
||||
- Smoke 每日摘要支援手動 Telegram 推播,並由 `momo-scheduler` 每日 09:10 呼叫 `run_ai_smoke_daily_summary_task()`。
|
||||
- 健康檢查 API 會將最近檢查結果保存到 JSONL,頁面顯示最近狀態趨勢。
|
||||
- 健康檢查歷史支援 JSONL 匯出、清理與每日「正常 / 注意 / 嚴重」摘要。
|
||||
- 健康檢查每日摘要支援手動 Telegram 推播,並由 `momo-scheduler` 每日 09:10 呼叫 `run_ai_smoke_daily_summary_task()`。
|
||||
- Grafana provisioning 新增 `docker/grafana/provisioning/dashboards/json/ai-automation-overview.json`,觀測 EventRouter dispatch/latency、safe action、Telegram replay 與 AutoHeal action/duration。
|
||||
- Active monitoring stack 使用 `monitoring/prometheus.yml` 的 `momo-app` job scrape `momo-pro-system:80/metrics`;Prometheus container 需加入 `momo-network`。
|
||||
- Active Blackbox HTTP targets 必須探測 `/health`(188 stack 目前 `https://mo.wooo.work/health` 與 `http://momo-pro-system:80/health`;110 gateway stack 目前 `https://mo.wooo.work/health`),不可探測 Dashboard 首頁 `/`,避免監控流量觸發重型 DB 查詢。
|
||||
@@ -104,7 +198,19 @@ SQL漏斗(~300筆)
|
||||
- Gunicorn runtime 預設 `worker_class = gthread`、`GUNICORN_THREADS=4`、`preload_app = False`;此組合讓 HUP 熱重載可用,也避免 Dashboard 長查詢完全阻塞 `/health`。
|
||||
- CD rebuild 模式必須先 build image 成功,再短暫 stop/rm/recreate 三應用容器,避免 no-cache build 造成長時間 502。
|
||||
- ElephantAlpha 使用 NVIDIA NIM hosted API;production 預設模型為 `nvidia/llama-3.3-nemotron-super-49b-v1.5`,`ELEPHANT_ALPHA_FALLBACK_MODELS` 需保留至少一個可呼叫備援;403/404、408/409/425/429、5xx、timeout 與 connection error 必須嘗試下一個模型。
|
||||
- OpenClaw/Hermes embedding 優先呼叫 Ollama `/api/embed`,只在舊節點不支援時 fallback `/api/embeddings`;timeout 由 `EMBEDDING_TIMEOUT` / `OLLAMA_EMBED_TIMEOUT` 控制。
|
||||
- ElephantAlpha L3 HITL 只允許發送有實證、可審核、可行動的升級告警;價格類 trigger 無 Hermes 具體威脅時,只記錄 suppressed escalation telemetry 與 cooldown,不寫 pending `human_review`,不發 Telegram 空告警。
|
||||
- ElephantAlpha 價格類 trigger 的 HITL / 決策 prefetch 必須先使用觸發 SQL 與 `competitor_prices` / `price_records` 的 DB 實證生成 SKU、MOMO / PChome 價差與建議 action lines;完整 Hermes LLM prefetch 預設關閉(`ELEPHANT_ALPHA_HERMES_LLM_PREFETCH_ENABLED=false`),避免 5s timeout 後落入無實證摘要或雲端備援。若無 DB 實證,只記錄 suppressed telemetry / cooldown,不發 Telegram 空告警。
|
||||
- ElephantAlpha `price_drop_alert` / `market_opportunity` trigger 不得對整張 `price_records` 做全表最新價聚合;必須先篩最近有效 `identity_v2` PChome 候選,再用 per-SKU `JOIN LATERAL` 讀最新 MOMO 價格,並把 `match_score`、`tags`、`match_diagnostic_json` 帶入 evidence。
|
||||
- ElephantAlpha 協調器收到非純 JSON、fenced JSON 或混文字 JSON 時,必須先做容錯抽取;仍無法解析時,只能使用 DB/Hermes 實證生成保守 HITL fallback。fallback 不得放入 OpenClaw `generate_*` 類舊策略步驟,也不得暗示已自動調價。
|
||||
- V10.624 起 ElephantAlpha 價格類 trigger 即使信心度達自主門檻,也只能發送 HITL 價格覆核通知;必須跳過 orchestrator `execution_plan` 內的 Hermes/NemoTron/OpenClaw 長任務 step。這是價格決策護欄:避免 60 秒 execution timeout 卡住 scheduler,也避免把價格策略誤描述為已自動執行。
|
||||
- ElephantAlpha 執行器若遇到舊版 OpenClaw strategy 類步驟(含 `generate_market_strategy` / `generate_dynamic_pricing_strategy` / `generate_resource_optimization_strategy`),只能記錄為 advisory skipped,不得觸發 circuit breaker,也不得轉成實際排程、外部呼叫或價格行動。
|
||||
- `resource_optimization` 不再交給 LLM 生成「預期效益 / 已執行」敘事,顯示名稱統一為「資源壓力治理」。此 trigger 必須先由程式量測 `action_plans` backlog、P1/P2 數、pending_review、逾時項目與 CPU load;只有 CPU 達門檻、P1/P2 積壓或逾時積壓才發 Telegram「資源壓力告警」。單純 queue 大但 CPU 正常只記錄 telemetry,不派發 Hermes/NemoTron、不宣稱 48 小時效益;Telegram 段落使用「系統處置紀錄」而非泛稱「已執行」,避免暗示 AI 已完成未經驗證的外部動作。
|
||||
- `resource_optimization` 的 Telegram 必須包含 `decision_envelope` 區塊,標明 `source_agent=elephant_alpha`、資料品質、量測證據、`can_auto_execute=false` 與 deterministic trace;此路徑不呼叫 Gemini、不呼叫 Hermes/NemoTron,也不得把 queue backlog 翻譯成主機資源耗盡。
|
||||
- `resource_optimization` 會先執行 `ActionPlanHygieneService` 清理過期噪音:只關閉超過 72 小時的 `code_review_fix` / `openclaw_recommendation` 類 advisory action_plans,以及 NemoTron `direct_response/reply_simple` 舊聊天回覆計畫;將狀態改為 `auto_disabled` 或 `rejected` 並寫入 `metadata_json.hygiene_history`。不刪資料,也不碰 NemoTron human_review / pricing / tool action 類業務行動。
|
||||
- `momo-scheduler` 每 6 小時固定執行 `run_action_plan_hygiene_task()`,讓過期 advisory action_plans 的關閉不再依賴 `resource_optimization` 告警觸發;排程失敗會經 EventRouter 發送 `action_plan_hygiene_failure`。
|
||||
- `action_plans` 產生端必須防重:Code Review 同一檔案已有 active `code_review_fix` 時不重建;OpenClaw recommendation 會寫入文字 fingerprint 並跳過同一建議;AIOrchestrator 不再把 NemoTron `direct_response/reply_simple` 聊天回覆存成 action plan,真正需工具、審核或執行的 NemoTron action 才能進 queue。
|
||||
- OpenClaw/Hermes embedding 優先呼叫 Ollama `/api/embed`,只在舊節點不支援時 fallback `/api/embeddings`;timeout 由 `EMBEDDING_TIMEOUT` / `OLLAMA_EMBED_TIMEOUT` 控制,並受 `OLLAMA_EMBED_MAX_TIMEOUT` 封頂。背景 worker / RAG 查詢不得落 111,除非 caller 顯式允許 `allow_111_fallback=True`。
|
||||
- PPT 自動產線由 `momo-scheduler` 依節奏執行 `run_ppt_auto_generation_task(schedule_kind)`:每日 20:30 產日報、週一 20:40 產週報/市場情報、每月 1 日 20:50 產月報與管理型簡報、季初 21:00 產季報、半年初 21:10 產半年報、年初 21:20 產年報,再交給 22:00 `ppt_vision_audit` 做視覺審核;每次嘗試會寫入 `ppt_generation_runs`,`/observability/ppt_audit_history` 以精準參數檢查目標版本是否已產生,並可用 `/observability/ppt_audit/generate_missing` 手動補齊缺漏,總開關為 `PPT_AUTO_GENERATION_ENABLED`。PPT vision 需 `PPT_VISION_ENABLED=true` 與容器內 LibreOffice;`/observability/ppt_audit_file/<filename>` 會把 PPTX 轉成 PDF 快取供站內線上預覽,原始 PPTX 仍保留下載。QA 失敗項目的「重跑」必須從檔名推回原 report_type,並只失效相同 `report_type + parameters` 的 active `ppt_reports` cache,避免拿到舊 PPT 或誤重跑 daily。
|
||||
|
||||
---
|
||||
|
||||
@@ -139,19 +245,20 @@ SQL漏斗(~300筆)
|
||||
> ⚠️ **架構限制**: `price_records` **只存 MOMO 自家售價**,無 `source` 欄位,無競品(PChome)價格。
|
||||
> PChome 比價資料必須由外部爬蟲即時抓取,以 `pchome_prices: dict` 形式注入 `HermesAnalystService.run()`。
|
||||
|
||||
### 2.3 `daily_sales_snapshot` 表(動態表,從 Excel 匯入)
|
||||
### 2.3 `daily_sales_snapshot` 表(PChome 後台業績匯出,動態表)
|
||||
|
||||
> **重要**: 此表由 `import_service.py` 使用 `df.to_sql()` 動態建立。
|
||||
> 欄位名稱**完全繼承自匯入的 MOMO Excel 報表原始欄位**,加上程式碼追加的 `snapshot_date`。
|
||||
> 欄位名稱**完全繼承自 PChome 後台匯出的業績 Excel 原始欄位**,加上程式碼追加的 `snapshot_date`。
|
||||
> V10.605 起,匯入器會掃描所有 worksheet 與前 15 列表頭,優先選擇含「日期 / 商品名稱 / 總業績或銷售金額」的明細工作表;格式或日期真的不合格的檔案會移到 Google Drive `匯入失敗`,避免每 30 分鐘重複告警。
|
||||
|
||||
#### 已確認的關鍵欄位(實際 MOMO 報表欄位名稱)
|
||||
#### 已確認的關鍵欄位(PChome 後台報表欄位名稱)
|
||||
|
||||
| 欄位 | 型別 | 說明 | 備注 |
|
||||
|------|------|------|------|
|
||||
| `snapshot_date` | Date | 資料所屬日期(程式追加) | 由 `import_service.py` 從「日期」欄位解析 |
|
||||
| `商品ID` | VARCHAR | **商品識別碼**(= `products.i_code`) | ⚠️ 非 `商品編號`!|
|
||||
| `商品ID` | VARCHAR | PChome 後台 / 訂單目錄商品識別碼 | ⚠️ 不可假設等於 `products.i_code` |
|
||||
| `商品名稱` | TEXT | 商品名稱 | |
|
||||
| `銷售金額` | NUMERIC | 銷售業績金額 | 系統以 find_col 模糊比對,優先 `銷售金額` |
|
||||
| `總業績` / `銷售金額` | NUMERIC | 銷售業績金額 | 匯入器以欄位群組模糊比對,兩者皆可 |
|
||||
| `數量` | NUMERIC | 銷售數量 | |
|
||||
| `總成本` | NUMERIC | 成本 | |
|
||||
| `廠商名稱` | VARCHAR | 廠商名稱 | |
|
||||
@@ -185,10 +292,10 @@ SKU/商品ID = find_col(['商品ID', 'Product ID', 'ID', 'i_code', 'Item Code'
|
||||
| `discount_pct` | INTEGER | 折扣 %(NULL=未折扣) |
|
||||
| `competitor_product_id` | VARCHAR(100) | PChome 商品 ID |
|
||||
| `competitor_product_name` | TEXT | PChome 商品名稱(核對用) |
|
||||
| `match_score` | NUMERIC(4,3) | 模糊比對分數(0~1),< 0.45 不寫入 |
|
||||
| `match_score` | NUMERIC(4,3) | 商品身份比對分數(0~1),< 0.76 不寫入正式快取 |
|
||||
| `tags` | JSONB | 語意標籤,如 `["on_sale","discount_20pct"]` |
|
||||
| `crawled_at` | TIMESTAMP | 爬取時間 |
|
||||
| `expires_at` | TIMESTAMP | TTL = crawled_at + 6h,過期後 Hermes 忽略 |
|
||||
| `expires_at` | TIMESTAMP | TTL = crawled_at + 48h(可由 `PCHOME_FEEDER_TTL_HOURS` 調整),過期後 Hermes 忽略;UI 身份覆蓋率不因價格 TTL 過期歸零 |
|
||||
|
||||
**UNIQUE**: `(sku, source)` — 同一 SKU+來源只有一筆,ON CONFLICT UPDATE
|
||||
|
||||
@@ -223,9 +330,11 @@ CREATE TABLE IF NOT EXISTS ai_price_recommendations (
|
||||
|
||||
---
|
||||
|
||||
## 三、SQL 漏斗設計(已修正欄位名稱)
|
||||
## 三、SQL 漏斗設計(PChome 業績 ID 邊界)
|
||||
|
||||
`hermes_analyst_service.py` → `fetch_candidates()` 的核心 SQL:
|
||||
`daily_sales_snapshot` 來自 PChome 後台業績匯出,`商品ID` 與 `products.i_code` 不保證同一套 ID。任何跨表分析必須先經過可驗證的 mapping / identity contract,不能只用字串相等把 PChome 後台銷售資料與 MOMO 商品主檔硬接。
|
||||
|
||||
歷史上曾以如下漏斗概念描述近 7 天銷售額下滑,但此 SQL 只能在 ID 已確認同源時使用:
|
||||
|
||||
```sql
|
||||
WITH latest_momo_price AS (
|
||||
@@ -263,10 +372,10 @@ ORDER BY (rs.sales_7d_curr - rs.sales_7d_prev) / rs.sales_7d_prev ASC
|
||||
LIMIT 300
|
||||
```
|
||||
|
||||
**漏斗效果**: 226萬筆 price_records → ~300 筆(近7天銷量跌幅 > 10% 的活躍商品)
|
||||
|
||||
**JOIN 邏輯**:
|
||||
- `products.i_code` ↔ `daily_sales_snapshot."商品ID"` — 均為 MOMO 商品代碼,格式相同
|
||||
**ID 邊界**:
|
||||
- `products.i_code` 是 MOMO 商品主檔 / 價格爬蟲 SKU。
|
||||
- `daily_sales_snapshot."商品ID"` 是 PChome 後台業績匯出中的商品識別碼。
|
||||
- 兩者不可被文件、Agent 或報表預設視為相同。需要合併分析時,必須先建立可審核 mapping 或沿用已驗證的 PChome identity / 商品名稱證據。
|
||||
|
||||
---
|
||||
|
||||
@@ -277,13 +386,13 @@ LIMIT 300
|
||||
```
|
||||
[competitor_price_feeder.py Worker] ←← 每 4 小時獨立運行
|
||||
↓ 搜尋 PChome(search_products)
|
||||
↓ 模糊比對(price_comparison.py)
|
||||
↓ 商品身份比對(marketplace_product_matcher.py)
|
||||
↓ 提取語意標籤
|
||||
↓ UPSERT competitor_prices(TTL 6h)
|
||||
↓ UPSERT competitor_prices(預設 TTL 48h)
|
||||
↓
|
||||
[HermesAnalystService.fetch_candidates()] ←← AI Pipeline 消費端
|
||||
↓ LEFT JOIN competitor_prices(零網路等待)
|
||||
↓ 有效期內(expires_at > NOW())+ match_score ≥ 0.45 才 JOIN
|
||||
↓ 有效期內(expires_at > NOW())+ match_score ≥ 0.76 + tags 含 identity_v2 才 JOIN
|
||||
↓ pchome_price + competitor_tags 一起傳給 Hermes
|
||||
```
|
||||
|
||||
@@ -293,19 +402,24 @@ LIMIT 300
|
||||
|------|------|------|
|
||||
| 解耦方式 | DB 表快取(非 Redis) | PostgreSQL 已是核心,無需額外依賴;支援 JOIN |
|
||||
| TTL | 6 小時 | 與 AI Pipeline 排程週期對齊 |
|
||||
| 比對算法 | 品牌(0.4) + 規格(0.3) + 關鍵字(0.3) | 依賴現有 `price_comparison.py` |
|
||||
| 最低比對門檻 | 0.45 | 低於此分數不寫入,避免張冠李戴影響 AI 決策 |
|
||||
| 比對算法 | 品牌 + 核心 token + 容量/重量/包數 + 品類 + 價格 sanity check | 由 `marketplace_product_matcher.py` 統一供 feeder、legacy crawler、AI/PPT 鏈路使用 |
|
||||
| 最低比對門檻 | 0.76 | 核心比價寧可待審,不允許低信心錯配影響 AI 決策 |
|
||||
| 已有不同 PChome 商品覆蓋門檻 | 0.84 | 新候選與既有正式配對不同時,除非超高信心,否則寫入 `needs_review` attempt 不覆蓋 |
|
||||
| 單位價可比模式 | `unit_comparable` | 同核心商品但買送/套組/件數不同時,不寫正式總價差;只寫入 attempt,並以單位價證據供 Dashboard / PPT / AI 報表與人工覆核 |
|
||||
| Browse.sh 診斷 | optional wrapper | 只用於 selector / XHR / network trace 探勘;不得取代正式 crawler,也不得直接把輸出寫成正式競品價格 |
|
||||
| 語意標籤 | JSONB 陣列 | 傳給 Hermes 提升情境感知品質 |
|
||||
|
||||
### 競品比對邏輯(`competitor_price_feeder.py`)
|
||||
|
||||
```
|
||||
MOMO 商品名稱[:20字]
|
||||
→ PChomeCrawler.search_products(keyword, limit=10)
|
||||
→ _find_best_match(momo_name, results)
|
||||
→ ProductNameParser(品牌 + 規格 + 關鍵字)
|
||||
→ _structural_similarity() → score
|
||||
→ score ≥ 0.45 → _upsert_competitor_price()
|
||||
MOMO 商品名稱
|
||||
→ marketplace_product_matcher.build_search_terms()
|
||||
→ PChomeCrawler.search_products(keyword, limit=12)
|
||||
→ marketplace_product_matcher.score_marketplace_match()
|
||||
→ 品牌衝突 / 容量衝突 / 包數衝突 hard veto
|
||||
→ 同核心但買送/套組/件數不同標記 unit_comparable,不進正式總價差
|
||||
→ 同款高信心 score ≥ 0.76 才進 competitor_prices
|
||||
→ 低信心、規格衝突、既有配對衝突寫入 competitor_match_attempts
|
||||
```
|
||||
|
||||
### `fetch_candidates()` v2 漏斗(已更新)
|
||||
@@ -315,16 +429,43 @@ LEFT JOIN competitor_prices cp
|
||||
ON cp.sku = lmp.sku
|
||||
AND cp.source = 'pchome'
|
||||
AND cp.expires_at > NOW()
|
||||
AND cp.match_score >= 0.45
|
||||
AND cp.match_score >= 0.76
|
||||
AND COALESCE(cp.tags, '[]'::jsonb) ? 'identity_v2'
|
||||
```
|
||||
→ 無競品資料的商品仍回傳,`pchome_price=NULL`,`_batch_analyze` 自動跳過
|
||||
|
||||
### 下游消費規範(2026-05-19 更新)
|
||||
|
||||
- Dashboard、AI pick、Hermes、Excel export、daily/growth 圖表與 competitor PPT 必須以 `competitor_prices + competitor_price_history + competitor_match_attempts` 為短期唯一生產真相源,且只消費 `identity_v2` matcher 驗證過的配對;舊版僅靠 `match_score` 的快取不可直接進入決策或簡報。
|
||||
- `pchome_matches` 與 live `pchome_batch()` 僅保留 legacy compatibility,不得作為新簡報或 AI 決策主來源。
|
||||
- `services/competitor_intel_repository.py` 是下游頁面、圖表、簡報的共用查詢出口;新增消費端不得各自硬寫不同 match threshold。所有競品報表的價差方向統一為 `MOMO - PChome`:正值代表 MOMO 較貴 / PChome 低價壓力,負值代表 MOMO 價格優勢;daily、growth、OpenClaw、PPT 不得使用反向定義。
|
||||
- competitor PPT 不可只輸出 matched rows 造成覆蓋率假象;`fetch_competitor_comparison_results()` 必須用 `LEFT JOIN valid_competitor` 保留高營收/高價但尚未有效配對的 MOMO 商品,並帶出 `match_status`、`candidate_count`、`best_match_score` 與 `match_diagnostic`,讓簡報與 AI 文案明確區分「高信心比對」與「待補身份/價格」。
|
||||
- `services/competitor_identity_revalidator.py` 可對既有 `competitor_prices` legacy row 離線重跑 `identity_v2`:只有新版 matcher 分數 `>= 0.76` 且無 hard veto 才補 `identity_v2` / `legacy_revalidated` tags;預設不刷新 `expires_at`,避免過期價格進入決策。
|
||||
- `CompetitorPriceFeeder.run_expired_identity_refresh()` 會優先刷新已通過 `identity_v2` 但 TTL 過期的 PChome row:直接用既有 `competitor_product_id` 批次呼叫 PChome 商品 API,再用新版 matcher 重新驗證名稱/規格/價格 sanity,通過後寫回 `competitor_prices` 與 `competitor_price_history`。這條路徑提升新鮮價格覆蓋率,但不降低 match threshold,也不讓過期價格直接進入決策;佇列排序必須先處理既有 `price_basis_total_price` / `alert_tier_price_alert_exact` 或 diagnostic 等價欄位的安全價差 row,再處理需要 review 的舊 row。若既有 `competitor_product_id` 已查不到或回傳候選低於門檻,expired refresh 只寫 `refresh_no_result` / 低信心 attempt 並標記 `fresh_search_recovery_deferred`,不得在同一條價格刷新路徑 fresh search 替換正式 identity。fresh search recovery 只保留給 retryable candidate revalidation / unmatched priority 等補抓路徑。
|
||||
- 過期 identity refresh 排序必須優先 `price_basis_total_price` / `alert_tier_price_alert_exact` 或 `match_diagnostic_json.price_basis='total_price'` / `alert_tier='price_alert_exact'` 的正式價差配對,再依 `expires_at` 與 MOMO 價格排序,避免高風險可決策價差長期排在低價或非告警型 stale row 後面。
|
||||
- `marketplace_product_matcher.py` 的擴充只能走「正向證據 + 反向 veto」:品牌一致、商品線/型號訊號強、價格合理且無 hard veto 時才允許 `strong_product_line_match` 加分;補充瓶/補充包/refill 與一般正裝不互相配對,分享組/加量組/明星組等組合包不得誤配單品。
|
||||
- 近門檻規則必須成對補「召回 + 防錯配」測試:可召回者需有品牌、商品線、規格或具名 identity anchor,例如 MUJI 精油芬香護手霜、Mustela 慕之幼爽身潤膚乳、Herbacin 小甘菊護手霜;防錯配者需成為 hard veto,例如 M·A·C Macximal 柔霧/緞光唇膏質地、ERBE 指甲清垢棒/指甲緣刨刀功能、Schick 舒芙/舒綺女用除毛刀品線。不得用單一同規格或同品牌放寬全域門檻。
|
||||
- 套組/買送/件數不同但品牌、核心商品線與單一基礎規格一致時,matcher 必須回傳 `comparison_mode='unit_comparable'` 與 `unit_comparable` reason;Feeder 只能寫入 `competitor_match_attempts.attempt_status='unit_comparable'` 或 `refresh_unit_comparable`,不得寫入 `competitor_prices`。Dashboard 與 `competitor_intel_repository` 必須用 `build_unit_price_comparison()` 產生每 ml / 每 g / 每入單位價證據,讓 PPT / AI 報表可說明「需單位價比較」而不是把總價當同款價差。商品看板在正式配對尚未成立時,仍必須顯示最佳候選 PChome 商品名稱、候選價與「候選價,需單位換算」說明,讓人工覆核可直接看見下一步;daily/growth、PPT 與 OpenClaw 摘要不得自建查詢,需消費 `fetch_competitor_review_queue()` 與 coverage 的 `unit_comparable_count`。若任一側含多個不同容量/重量規格,視為多品項套組,不可進 `unit_comparable`。
|
||||
- PChome feeder 的外部 request timeout 由 `PCHOME_FEEDER_TIMEOUT` 控制,預設 12 秒;排程不得因單一 PChome 搜尋 API timeout 被拖到數分鐘。
|
||||
- 品牌 alias 屬於正向身份證據,不是門檻放寬;`DR.WU / DR WU / DRWU / 達爾膚` 這類同品牌中英混寫必須正規化後再進 matcher,避免同規格真同款被誤降成 brandless identity review。
|
||||
- 近門檻 rescore pilot 必須支援明確 SKU 篩選;`audit_competitor_match_attempt_rescore.py --sku <sku>` 可只重算指定 SKU,避免為了小批次驗證而掃整批 `true_low_confidence`。
|
||||
- 商品看板的 PChome 狀態必須把 matcher 診斷原因翻成可行動語意:品牌不符已排除、規格不符已排除、補充包不相容、組合規格不相容、系列不符已排除、需單位價比較、低信心待補強等,不可只顯示籠統「待比對」或「身份否決」。
|
||||
- PChome 補抓產線與 priority list 若尚未進入搜尋/補抓,必須顯示「PChome 補抓產線」、「尚未搜尋」與「尚未進入 PChome 補抓」,不得使用「待比對」這類會被誤解成已有候選待人工審核的字眼。
|
||||
- 商品看板、PChome review queue 與 `/api/export/excel/pchome-review` 必須優先讀取 `match_diagnostic_json.reasons` 並轉成操作員可讀標籤;文字版 `error_message` 只作 legacy fallback。商品列的 PChome 狀態摘要也必須使用同一套專業標籤,避免 overview 顯示「妝效質地不同」但列表仍顯示籠統身份不符。新增 matcher reason 時需同步更新 `MATCH_DIAGNOSTIC_REASON_LABELS` 與 dashboard 狀態翻譯,避免 UI 顯示 `makeup_finish_conflict` 這類 machine code。PChome 標題缺品牌但有窄範圍 exact identity anchor 的商品,只能透過具名 brandless recovery 進 manual-review identity;多色任選 / 單一色號 gap 必須標記 `variant_selection_review`,並從 `recoverable_low_score` 降回 `true_low_confidence`,不得自動批次寫正式價差。
|
||||
- Dashboard 必須把「待比對」拆成可診斷狀態:`價格過期待刷新`、`舊版配對待重驗`、`低分配對待補強`、`已排除`、`需單位價比較`、`找不到同款`、`抓取異常`、`尚未搜尋`。硬性不相容候選應顯示為已排除/不相容,不得讓使用者誤以為每筆都需要人工待審。
|
||||
|
||||
### 執行方式
|
||||
|
||||
```bash
|
||||
# 手動觸發一輪抓取
|
||||
python3 services/competitor_price_feeder.py
|
||||
|
||||
# 預覽 legacy PChome 快取 identity_v2 重驗證(不寫入)
|
||||
python3 -m services.competitor_identity_revalidator --limit 500
|
||||
|
||||
# 寫入安全通過的 identity_v2 tag;不刷新過期價格
|
||||
python3 -m services.competitor_identity_revalidator --limit 500 --apply
|
||||
|
||||
# 未來整合為 K3s CronJob(每 4 小時)
|
||||
# k8s/jobs/competitor-price-feeder-cronjob.yaml
|
||||
```
|
||||
@@ -339,6 +480,9 @@ python3 services/competitor_price_feeder.py
|
||||
2. **倒金字塔結構** — 結論先行 → 核心數據 → AI 洞察 → 建議行動 → 運算足跡
|
||||
3. **收斂行動呼籲 (Call to Action)** — 每則訊息只有一個明確的 👉 建議行動
|
||||
4. **底部運算足跡** — FinOps + Observability,用分隔線隔開主訊息
|
||||
5. **EA HITL 專業 brief** — `ea_escalation` 必須分成決策狀態、背景摘要、風險摘要、TOP 待審 SKU 與建議處置;價格類行動不得用長 bullet 串接,必須拆出 MOMO/PChome 價格、價差、人工處置與 PChome ID。
|
||||
6. **價格類決策信封專業 brief** — `price_alert`、`pchome_match_review`、`competitor_price_review` 等含 PChome / 價格證據的 `decision_envelope`,EventRouter 必須直送 evidence template,不得進 L1/L2 重摘要;Telegram 內容必須拆成「標的、價格證據、比對證據、人工下一步」,並從信封讀取 `momo_price`、`competitor_price`、`candidate_gap_pct`、`match_score`、`unit_price_insight` 與 `existing_match_conflict`。
|
||||
7. **Shared UI 信封摘要共用** — Webcrumbs host data 與其他共用 UI runtime 必須讀 `reviewDecisionBrief` / `decision_envelope` 的結構化證據,不在瀏覽器端重組比價判斷;這些 payload 只能讀 DB 既有覆核資料,不呼叫 LLM、不抓外站、不寫資料。
|
||||
|
||||
### 5.2 語意化 Emoji 字典
|
||||
|
||||
@@ -466,7 +610,7 @@ python3 services/competitor_price_feeder.py
|
||||
### 告警群組
|
||||
- 群組: **小龍蝦** (業務情報專用,非 SRE 維運)
|
||||
- Chat ID: `-1003940688311`
|
||||
- Bot: `8610496165:AAFOlcWV4oRUSC2TI-fYux7JV97fjNzsYR8`
|
||||
- Bot: `<TELEGRAM_BOT_TOKEN>`
|
||||
|
||||
### 單 Bot 多身份策略(One Bot, Multiple Headers)
|
||||
| 模組 | Telegram 標頭 |
|
||||
@@ -490,7 +634,7 @@ python3 services/competitor_price_feeder.py
|
||||
| 參數 | 值 |
|
||||
|------|---|
|
||||
| 模型 | `hermes3:latest` |
|
||||
| Ollama URL | GCP-A `http://34.143.170.20:11434` → GCP-B `http://34.21.145.224:11434` → 111 `http://192.168.0.111:11434` |
|
||||
| Ollama URL | GCP-A `http://34.87.90.216:11434` → GCP-B `http://34.21.145.224:11434` → 111 `http://192.168.0.111:11434` |
|
||||
| Timeout | 120s |
|
||||
| Temperature | 0.1 |
|
||||
| 實測推理時間 | **19.3s(3筆,實彈 2026-04-17)** |
|
||||
@@ -518,7 +662,8 @@ python3 services/competitor_price_feeder.py
|
||||
| ✅ | momo-app 雙網路連線 | 同時連 `momo-network` + `momo-pro_default`(後者含 `momo-db` alias `momo-postgres`)|
|
||||
| P1 | PChome Feeder CronJob | `competitor_price_feeder.py` 每 4 小時排程 (Scheduler 整合) |
|
||||
| P1 | 告警去重 TTL | 同一 SKU 短期內重複告警未防範 |
|
||||
| P1 | `daily_sales_snapshot` 欄位防禦 | 若 Excel 欄位名變更,JOIN 條件會靜默失效 |
|
||||
| ✅ | `daily_sales_snapshot` 欄位防禦 | V10.605 已支援多 worksheet / 表頭列掃描 / 格式失敗隔離 |
|
||||
| P1 | PChome 後台業績匯出前半段自動化 | 現有 importer 已自動吃 Google Drive;仍需接 PChome 後台排程匯出、Email 附件或受控 browser 下載 |
|
||||
| P2 | Scheduler 整合 | 每6小時自動觸發 Hermes→NIM→Telegram 管線 |
|
||||
| P2 | Gemini 備援治理 | 僅保留 ADR-028 鎖定場景與 Ollama 失敗備援,新增 caller 必須走 ADR |
|
||||
|
||||
@@ -533,7 +678,7 @@ python3 services/competitor_price_feeder.py
|
||||
| PostgreSQL | 192.168.0.188 | `momo-db` | pgvector/pgvector:pg14,含所有 AI 相關表 |
|
||||
| momo-app | 192.168.0.188 | `momo-pro-system` | **Up healthy,port 5002:80**(5001 被 docker-registry 佔用,已改 5002) |
|
||||
| momo-scheduler | 192.168.0.188 | `momo-scheduler` | 常駐排程容器 |
|
||||
| Ollama Primary | 34.143.170.20 | Ollama 原生 | GCP-A,AI/LLM/embedding 主路徑 |
|
||||
| Ollama Primary | 34.87.90.216 | Ollama 原生 | GCP-A,AI/LLM/embedding 主路徑 |
|
||||
| Ollama Secondary | 34.21.145.224 | Ollama 原生 | GCP-B,同等備援 |
|
||||
| Ollama Fallback | 192.168.0.111 | Ollama 原生 | 最後一道本地防線 |
|
||||
| E2E 驗證容器 | 192.168.0.188 | `momo-e2e-test` | 臨時容器,含新服務模組 |
|
||||
@@ -541,9 +686,9 @@ python3 services/competitor_price_feeder.py
|
||||
### 188 `/home/ollama/momo-pro/.env` 正確設定
|
||||
|
||||
```bash
|
||||
TELEGRAM_BOT_TOKEN=8610496165:AAFOlcWV4oRUSC2TI-fYux7JV97fjNzsYR8 # ← 唯一正確 token
|
||||
TELEGRAM_BOT_TOKEN=<TELEGRAM_BOT_TOKEN> # ← 唯一正確 token
|
||||
TELEGRAM_CHAT_IDS=["-1003940688311"] # 小龍蝦群組
|
||||
NVIDIA_API_KEY=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
|
||||
NVIDIA_API_KEY=<NVIDIA_API_KEY>
|
||||
USE_POSTGRESQL=true
|
||||
POSTGRES_HOST=momo-db
|
||||
# POSTGRES_DB / USER / PASSWORD 使用 docker-compose.yml 預設值
|
||||
@@ -558,7 +703,7 @@ POSTGRES_HOST=momo-db
|
||||
|
||||
| 日期 | 問題 | 修正 |
|
||||
|------|------|------|
|
||||
| 2026-04-17 | `fetch_candidates()` SQL 使用 `"商品編號"` | 修正為 `"商品ID"`(與 MOMO Excel 實際欄位名一致) |
|
||||
| 2026-04-17 | `fetch_candidates()` SQL 使用 `"商品編號"` | 修正為 `"商品ID"`;2026-06-15 追認此欄位來自 PChome 後台業績匯出,不可預設等於 MOMO `products.i_code` |
|
||||
| 2026-04-17 | Hermes gap_pct 由 LLM 計算 → 誤差大 | 改為 Python 預算 `(momo-pchome)/pchome*100` |
|
||||
| 2026-04-17 | 推理時間 52s | 預算 gap_pct 後降至 19.3s (3筆) |
|
||||
| 2026-04-17 | `model_footprint` DB 欄位寫入 `{}` | 分離 `footprint_text`(Telegram 顯示)與 `footprint_data`(DB JSON)|
|
||||
@@ -567,3 +712,11 @@ POSTGRES_HOST=momo-db
|
||||
| 2026-04-17 | 188 容器無 volume mount,`docker cp` 臨時解 | 重建 image(`COPY . .` bake 進新代碼);port 5001 衝突記錄為技術債 |
|
||||
| 2026-04-17 | 188 .env Telegram token 不正確(split-brain)| 修正為 `8610496165`,188→Telegram message_id=282 確認 |
|
||||
| 2026-04-17 | NIM Tool Calling E2E | 真實 NVIDIA_API_KEY 驗證:dispatched=3, errors=[] |
|
||||
| 2026-05-20 | PChome 商品身份比對仍可能因單一規格重疊誤放行 | V10.312 起 matcher 解析 mg/mcg 劑量、件組套組、多規格集合與同數字不同單位;劑量/容量/重量/件數/品類衝突會硬否決或導向單位價覆核,避免錯配污染 Dashboard、daily/growth、PPT 與 AI 競價分析 |
|
||||
| 2026-05-20 | 正確 PChome 候選常因只掃第一頁或搜尋詞丟失品牌/規格而未進入 matcher | V10.314 起搜尋 API 依 limit 掃多頁、對暫時性錯誤有限重試;feeder 預設 5 組搜尋詞、20 候選、2 頁,並保留括號/方括號內品牌與規格,提升覆核隊列與正式比價的候選品質 |
|
||||
| 2026-05-20 | 指定日期競品簡報可能混用目前 `competitor_prices` 快取價 | V10.315 起 `fetch_competitor_comparison_results()` 有 start/end date 時改用 `competitor_price_history` 期間快照,MOMO 價格取報表結束日前最新價;即時報表才使用目前有效 `competitor_prices` |
|
||||
| 2026-05-20 | PChome 覆蓋率分子可能被非活躍或無 MOMO 現價 SKU 膨脹 | V10.317 起 `fetch_competitor_coverage()` 的 `valid_matches` 改為 active MOMO latest price 與有效 PChome `identity_v2` 價格交集,確保 daily/growth/PPT/AI 看到的比價資料品質不被舊快取列高估 |
|
||||
| 2026-05-20 | EA HITL 告警可能把非 SKU 診斷誤排成待審 SKU,或在缺少 DB/Hermes 實證時打擾人工 | V10.318 起 `ea_escalation` 僅對含 SKU/價格比較的 actions 使用競價卡片;非 SKU 診斷改為「待確認事項」。價格類低信心事件若無 DB/Hermes 實證,測試鎖定只 suppress、不寫 human_review、不發 Telegram |
|
||||
| 2026-05-21 | ElephantAlpha NIM/LLM 回應偶爾不是純 JSON,會觸發 `json.loads()` 失敗並落入舊式空泛策略 fallback | V10.383 起協調器容忍 fenced/混文字 JSON;無法解析時改用 DB/Hermes 實證 fallback,且 fallback 不再包含 OpenClaw `generate_*` 舊步驟或自動調價暗示 |
|
||||
| 2026-05-20 | Telegram HTML parse mode 不支援 `<br>`,可能導致告警或報告送出 400 | V10.321 起 Telegram template 發送前會把 `<br>` / `<br/>` / `<BR />` 轉為換行;保留其他 HTML 標籤,非 HTML parse mode 不改寫 |
|
||||
| 2026-05-20 | 部分舊 Telegram 入口繞過中央 sanitizer,且 RAG awaiting review 使用錯誤 `chat_id=` 參數會讓人工審核推播失敗 | V10.322 起 Bot API price decision 走 `send_telegram_with_result()`;`price_decision()` 補 `report_url` 相容並 escape 動態欄位;RAG awaiting review 改用 `chat_ids=[...]` 呼叫 `_send_telegram_raw()` |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## ✅ 已完成的設定
|
||||
|
||||
### 1. Telegram Bot 配置
|
||||
- **Bot Token**: 8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg
|
||||
- **Bot Token**: <TELEGRAM_BOT_TOKEN>
|
||||
- **接收者 Chat ID**:
|
||||
- 5619078117
|
||||
- 961168381
|
||||
|
||||
@@ -686,7 +686,7 @@ OLLAMA_CONFIG = {
|
||||
'base_url': 'http://192.168.0.188:11434', # 內部 Ollama 伺服器
|
||||
'model': 'gemma3:4b',
|
||||
'timeout': 120,
|
||||
'api_key': '0df8b4f247a4497998248f013ce92a17.vqSWDEK0RppTZIwcdT-ei-Sz'
|
||||
'api_key': '<OLLAMA_API_KEY>'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1409,7 +1409,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
- **IP**: 192.168.0.188
|
||||
- **Port**: 11434
|
||||
- **Model**: gemma3:4b
|
||||
- **API Key**: `0df8b4f247a4497998248f013ce92a17.vqSWDEK0RppTZIwcdT-ei-Sz`
|
||||
- **API Key**: `<OLLAMA_API_KEY>`
|
||||
|
||||
### 網路設定確認
|
||||
```bash
|
||||
@@ -1426,7 +1426,7 @@ curl http://192.168.0.188:11434/api/generate \
|
||||
# Ollama 伺服器設定
|
||||
OLLAMA_BASE_URL = os.getenv('OLLAMA_BASE_URL', 'http://192.168.0.188:11434')
|
||||
OLLAMA_MODEL = os.getenv('OLLAMA_MODEL', 'gemma3:4b')
|
||||
OLLAMA_API_KEY = os.getenv('OLLAMA_API_KEY', '0df8b4f247a4497998248f013ce92a17.vqSWDEK0RppTZIwcdT-ei-Sz')
|
||||
OLLAMA_API_KEY = os.getenv('OLLAMA_API_KEY', '<OLLAMA_API_KEY>')
|
||||
OLLAMA_TIMEOUT = int(os.getenv('OLLAMA_TIMEOUT', '120'))
|
||||
```
|
||||
|
||||
|
||||
@@ -45,9 +45,13 @@
|
||||
蓋掉原本的 plan 元流程文字。**強制配套限制**:
|
||||
|
||||
- `asyncio.wait_for(timeout=5)` 短超時:Hermes 熱駐留 < 10s,但冷啟動會拖到 30s+,HITL 訊息延遲不可大於 10s
|
||||
- Pre-fetch 失敗(timeout / 0 threats / 全部缺金額)→ fallback 回原 plan 文字,**不中斷 escalation 主流程**
|
||||
- Pre-fetch 失敗(timeout / 0 threats / 全部缺金額)→ **不送 Telegram、不寫 pending human_review**,只記錄 suppressed escalation telemetry 與 cooldown,避免把無實證 plan 當成可審核告警
|
||||
- 「全部行皆缺金額」也視同無料 fallback,避免「乾巴巴兩行 MOMO/PChome 比價」比 plan 文字更空泛
|
||||
|
||||
### 規則 1.1 — 非價格類低信心 escalation 必須有可審核內容
|
||||
|
||||
2026-05-19 補充:`resource_optimization` 不是 SKU/價格事件,不能套用 Hermes/SKU fallback 模板。當 EA 對 `resource_optimization` 只產出低信心決策且沒有具體可審核行動時,系統只記錄 telemetry 與 cooldown,不發 Telegram,也不建立 `ai_insights.status='pending'` 的人工審核。這避免「資源調配優化」告警顯示「Hermes 即時威脅清單不可用」這類錯誤診斷。
|
||||
|
||||
### 規則 2 — NemoTron 告警必填金額影響量化
|
||||
|
||||
新增模組級 helper `_compute_business_impact(threat) -> {revenue_loss_7d, recommended_price}`:
|
||||
@@ -96,6 +100,7 @@
|
||||
### 正面
|
||||
|
||||
- EA 升級審核 Telegram 內容從元流程描述變為「具體 SKU + 價格 + 金額流失 + 建議調價」,HITL 真正可決策
|
||||
- 無實證的價格類與資源調配低信心 escalation 不再打擾人工,避免 Telegram 出現不可批准、不可駁回、不可操作的空告警
|
||||
- NemoTron 既有告警再升級,每筆都帶可批准/駁回的金額判斷依據
|
||||
- `momo:eig:` 按鈕首次有對應 handler,HITL 流程閉環完整
|
||||
- pre-fetch 改用 5s 短超時 + fallback,最壞情況退回原 plan 文字,不破壞既有行為
|
||||
@@ -103,7 +108,7 @@
|
||||
### 負面 / 風險
|
||||
|
||||
- 每次價格類 escalation 多花 ≤ 5s(Hermes 熱駐留實測 < 10s 但有 timeout),整體告警延遲略增
|
||||
- Hermes 在 5s 內若沒回應,告警內容降級回 plan 文字(仍維持原行為,無新增風險)
|
||||
- Hermes 在 5s 內若沒回應,價格類 escalation 會被壓制並記錄 telemetry;若後續需要追查,需從 `ai_calls.meta.suppressed_escalation` 與 scheduler log 觀察
|
||||
- `gap_pct ≤ 0` 案例的銷量下滑(非價格因素)將完全不顯示流失金額——若統帥需追蹤「非價格流失」需另開告警類型(待後續 ADR)
|
||||
|
||||
### 監控指標
|
||||
@@ -118,6 +123,7 @@
|
||||
- [x] `services/nemoton_dispatcher_service.py` `_compute_business_impact` helper + 三條 dispatch 路徑注入
|
||||
- [x] `services/elephant_alpha_autonomous_engine.py` `_fetch_hermes_threats_summary` + 5s timeout + fallback
|
||||
- [x] `services/telegram_bot_service.py` `_handle_event_ignore_callback` + HTML escape + 空 id 拒絕
|
||||
- [x] 2026-05-19:無實證價格類 / `resource_optimization` 低信心 escalation 改為 suppressed telemetry,不再送空泛 Telegram
|
||||
- [x] Critic 審查通過(Critical-1 / High-1 / High-2 / Medium-2 / Medium-3 全修)
|
||||
- [x] Smoke test:`_compute_business_impact` 對 gap≤0 / gap=0 / 銷量回升 / bogus type 四案例驗證
|
||||
- [x] `docs/adr/README.md` 索引加 ADR-021
|
||||
|
||||
@@ -43,6 +43,7 @@ EwoooC 目前已有 MOMO EDM / 節慶活動資料、`promo_products`、PChome
|
||||
- `market_product_price_history`
|
||||
- `market_product_matches`
|
||||
- `market_crawler_runs`
|
||||
- `market_alert_review_queue`
|
||||
|
||||
`promo_products` 只作為既有 MOMO 活動資料的相容來源或雙寫過渡,不再承接跨平台唯一真相。
|
||||
|
||||
@@ -151,6 +152,106 @@ EwoooC 目前已有 MOMO EDM / 節慶活動資料、`promo_products`、PChome
|
||||
- 2026-05-12 追加 read-only DB schema probe:`/api/market_intel/schema_db_probe` 預設只回 planned,不連 DB;人工 smoke 才能以明確參數查正式 DB catalog。探針不得使用 `DatabaseManager()`,避免觸發 metadata `create_all()`;不得建立 ORM session、不得寫入、不得 commit。
|
||||
- 2026-05-12 追加 platform seed DB diff probe:`/api/market_intel/platform_seed_db_diff` 預設只回 planned,不連 DB;人工 smoke 才能以明確參數只讀查詢 `market_platforms`,比對 adapter seed 是否 missing / differs / matches。探針不得使用 `DatabaseManager()`、不得建立 ORM session、不得寫入、不得 commit。
|
||||
- 2026-05-13 追加 platform seed CLI writer:`scripts/market_intel_seed_writer.py` 可在 CLI 明確帶入 `--execute`、`--apply-real-write` 與確認 token 時,以 SQLAlchemy Core 短 transaction upsert `market_platforms`;API 仍不得替使用者執行 DB 寫入,不建立 ORM session、不連外、不掛 scheduler。
|
||||
- 2026-05-18 追加 legacy source bridge preview:`/api/market_intel/legacy_source_bridge` 預設 `execute=false` 只回 planned,不連 DB;人工 smoke 才能以 `execute=true` 只讀盤點 `promo_products`、`competitor_prices`、`competitor_price_history`,產生舊資料導入 `market_*` 的 mapping、dedupe 與 blocked operation preview。此橋接不得寫入 DB、不得建立 ORM session、不得把 PChome 比價快取冒充為活動頁商品、不得掛 scheduler。
|
||||
- 2026-05-18 追加 MCP readiness preview:`/api/market_intel/mcp_readiness` 預設 `execute=false` 只回 planned,盤點 ADR-031 外部 MCP server、`services.mcp_router` feature flag、tool registry、`mcp_calls` telemetry 與 market_intel tool contract 缺口。人工 smoke 才能以 `execute=true` 做只讀 health / telemetry probe;此探針不得寫 DB、不得建立 ORM session、不得替市場情報自動啟用 MCP 或外部爬取。
|
||||
- 2026-05-18 追加 internal MCP tool contract preview:`services.market_intel.mcp_contract` 與 `/api/market_intel/mcp_tool_contract` 定義 `market_campaign_search`、`market_campaign_scrape`、`market_product_match_lookup` 三個 read-only contract,並在 `services.mcp_router.TOOL_REGISTRY` 註冊 `market_intel` caller 白名單。此階段只建立可審核合約與 readiness 檢查,不啟用 `MCP_ROUTER_ENABLED`、不呼叫 MCP server、不寫 DB、不掛 scheduler。
|
||||
- 2026-05-18 追加 external MCP deploy preflight preview:`services.market_intel.mcp_deploy_preflight` 與 `/api/market_intel/mcp_deploy_preflight` 只讀檢查 `docker-compose.mcp.yml`、必要 env、localhost-only ports、read-only volume、Firecrawl resource guard 與 fallback plan。`docker-compose.mcp.yml` 需以 read-only bind mount 進 app 容器供 preflight 審核。此 preflight 不執行 docker/SSH、不建立 `mcp_readonly` role、不啟用 `MCP_ROUTER_ENABLED`、不寫 DB、不掛 scheduler;外部 MCP stack 須等 env 與 operator smoke 全過後另行批准。
|
||||
- 2026-05-18 追加 MCP activation runbook preview:`services.market_intel.mcp_activation_runbook` 與 `/api/market_intel/mcp_activation_runbook` 只輸出人工啟用順序與 gate:補必要 env、人工建立/驗證 `mcp_readonly`、啟動外部 MCP stack、四個 health 全過、最後才允許 `MCP_ROUTER_ENABLED=true`。此 runbook 不執行 docker/SSH、不寫 env、不建立 DB role、不跑 health、不啟用 router、不寫 DB、不掛 scheduler。
|
||||
- 2026-05-18 追加 MCP fetch gate preview:`services.market_intel.mcp_fetch_gate` 與 `/api/market_intel/mcp_fetch_gate` 將人工 discovery fetch 改成必須先通過市場情報 feature flags、MCP readiness、router、外部 MCP health 與 tool contract gate;`run_manual_discovery(fetch=true)` 即使 flags 開啟也會先被此 gate 阻擋,直到 MCP 條件全過。此 gate 不抓電商頁、不寫 DB、不掛 scheduler、不執行 deployment,UI 預設只呼叫 `fetch=false&execute=false`。
|
||||
- 2026-05-24 追加 MCP manual fetch handoff gate:`services.market_intel.mcp_manual_fetch_handoff` 與 `/api/market_intel/mcp_manual_fetch_handoff` 將 runtime promotion package 與操作員安全確認合併成可審核 handoff,只放行到人工 fetch gate operator review;API/UI 不保存 payload、不打 health、不開 DB、不抓外站、不掛 scheduler,也不會自動打開 manual fetch。
|
||||
- 2026-05-24 追加 MCP fetch target review gate:`services.market_intel.mcp_fetch_target_review` 與 `/api/market_intel/mcp_fetch_target_review` 在 manual fetch handoff 後審核 adapter registry 公開入口白名單、平台來源、每平台 delay / timeout / max pages / sample_limit、rollback plan 與操作員確認;API/UI 不保存 payload、不發外部 request、不開 DB、不寫入、不掛 scheduler,也不會自動打開 manual fetch,只放行到後續人工 fetch run package review。
|
||||
- 2026-05-24 追加 MCP fetch run package gate:`services.market_intel.mcp_fetch_run_package` 與 `/api/market_intel/mcp_fetch_run_package` 將已通過的 target review 轉成 command argv preview、receipt path 與操作員 run controls 審核包;路由拆至 `routes.market_intel_mcp_run_routes`,主 Blueprint 僅掛 extension import。此階段不執行 CLI、不發外部 request、不寫 artifact、不開 DB、不掛 scheduler,只放行到後續 run readiness review。
|
||||
- 2026-05-24 追加 MCP fetch run readiness gate:`services.market_intel.mcp_fetch_run_readiness` 與 `/api/market_intel/mcp_fetch_run_readiness` 在 run package 後檢查 command preview、receipt path、artifact path、節流/timeout/dry-run-first、secret payload 與操作員 shell-only 邊界;此階段不執行 CLI、不發外部 request、不寫 artifact、不開 DB、不掛 scheduler,只放行到人工 shell dry-run 與後續 receipt review。
|
||||
- 2026-05-29 追加 MCP fetch run receipt gate:`services.market_intel.mcp_fetch_run_receipt` 與 `/api/market_intel/mcp_fetch_run_receipt` 在操作員 shell 完成 dry-run fetch 後審核 receipt id、artifact path、platform/source/receipt path 對帳、公開 URL、request/error budget、secret 外洩與 API/DB/scheduler 副作用旗標;API/UI 不執行 CLI、不發外部 request、不保存 receipt、不開 DB、不寫入、不掛 scheduler,只放行到後續 result parser review。
|
||||
- 2026-05-31 追加 MCP fetch result parser review gate:`services.market_intel.mcp_fetch_result_parser_review` 與 `/api/market_intel/mcp_fetch_result_parser_review` 在 receipt 通過後審核操作員貼回的 parser 結構化摘要,檢查 receipt source/receipt path 對帳、parser artifact path、活動/商品候選必要欄位、公開 URL、小批次候選上限、raw HTML/secret 外洩與 API/DB/scheduler 副作用旗標;API/UI 不讀 artifact、不執行 parser CLI、不發外部 request、不保存 parser result、不寫入、不掛 scheduler,只放行到候選 handoff review。
|
||||
- 2026-05-31 追加 MCP fetch candidate handoff review gate:`services.market_intel.mcp_fetch_candidate_handoff_review` 與 `/api/market_intel/mcp_fetch_candidate_handoff_review` 在 parser review 通過後審核候選交接包,檢查 source/candidate key 對齊、queue policy 是否仍是 `manual_candidate_review` / `preview_only`、小批次候選上限、操作員無寫入/無連外/無排程確認、raw HTML/secret 外洩與 side-effect flags;API/UI 不建立 queue、不讀 artifact、不寫 DB、不掛 scheduler,只放行到人工 candidate queue review。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue review gate:`services.market_intel.mcp_fetch_candidate_queue_review` 與 `/api/market_intel/mcp_fetch_candidate_queue_review` 在 handoff review 通過後審核人工 queue review 草案,檢查候選 key 對齊、`review_state=needs_review`、allowed actions 人工限定、`queue_write_status=not_persisted`、操作員無寫入/無連外/無排程確認、raw HTML/secret 外洩與 side-effect flags;API/UI 不建立 queue、不更新 review_state、不讀 artifact、不寫 DB、不掛 scheduler,只放行到 writer preflight。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer preflight gate:`services.market_intel.mcp_fetch_candidate_queue_writer_preflight` 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_preflight` 在 queue review 通過後審核 writer preflight 草案,檢查 `target_table=market_alert_review_queue`、`write_mode=cli_only_later`、dedupe strategy、insert columns、payload rows、候選 key 對齊、小批次上限、操作員無寫入/無連外/無 CLI/無排程確認、raw HTML/secret 外洩與 side-effect flags;API/UI 不開 DB、不執行 CLI、不建立 queue、不更新 review_state、不寫 DB、不掛 scheduler,只放行到 CLI writer review。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer CLI review gate:`services.market_intel.mcp_fetch_candidate_queue_writer_cli_review` 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_cli_review` 在 writer preflight 通過後審核 CLI review 草案,檢查 script path、target table、preflight id、payload row count、candidate/dedupe keys、`--sample-json`、`--read-only-preflight` 與 forbidden flags;API/UI 不執行 CLI、不讀 approval token、不寫檔、不開 DB、不寫 queue、不掛 scheduler,只放行到 operator run package review。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer run package review gate:`services.market_intel.mcp_fetch_candidate_queue_writer_run_package_review` 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_run_package_review` 在 CLI review 通過後審核 operator run package 草案,檢查 package identity、artifact manifest、operator shell command sequence、candidate/dedupe keys、CLI review id 與 preflight id;API/UI 不產檔、不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不掛 scheduler,只放行到 run readiness review。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer run readiness gate:`services.market_intel.mcp_fetch_candidate_queue_writer_run_readiness` 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_run_readiness` 在 run package review 通過後審核 operator readiness 證據,檢查 run readiness artifact、reviewed sample、備份、read-only preflight、post-write smoke 路徑、操作員覆核確認、CLI-only 與 approval token shell-only 邊界;API/UI 不產檔、不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不掛 scheduler,只放行到後續 run receipt review。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer run receipt review gate:`services.market_intel.mcp_fetch_candidate_queue_writer_run_receipt_review` 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_run_receipt_review` 在 run readiness 通過後審核 operator shell writer run 的 receipt 摘要,檢查 readiness linkage、run package id、候選/dedupe keys、writer output artifact、post-write smoke artifact、backup path、operator confirmations 與 token redaction;API/UI 不讀 receipt 原文、不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不做 post-write query、不掛 scheduler,只放行到 closeout review。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer run closeout review gate:`services.market_intel.mcp_fetch_candidate_queue_writer_run_closeout_review` 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_run_closeout_review` 在 receipt review 通過後審核 operator closeout 摘要,檢查 receipt linkage、closeout artifact、receipt review artifact、post-closeout inventory plan、writer output / post-write smoke / backup manifest、rollback note 與 operator confirmations;API/UI 不讀 receipt 原文、不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不做 post-closeout query、不掛 scheduler,只放行到 read-only post-closeout inventory review。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer post-closeout inventory review gate:`services.market_intel.mcp_fetch_candidate_queue_writer_post_closeout_inventory_review` 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_post_closeout_inventory_review` 在 closeout review 通過後審核 operator live inventory read-only 摘要,檢查 closeout linkage、row count、inventory artifact、closeout review artifact、read-only query result、missing/duplicate rows 與 operator confirmations;API/UI 不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不做 inventory query、不掛 scheduler,只放行到 candidate queue review handoff。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer review handoff gate:`services.market_intel.mcp_fetch_candidate_queue_writer_review_handoff` 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_review_handoff` 在 post-closeout inventory review 通過後審核 operator candidate queue review handoff 摘要,檢查 inventory linkage、handoff identity、target table、row count、artifact paths、review contract、forbidden API actions 與 operator confirmations;API/UI 不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不更新 review_state、不掛 scheduler,只放行到人工 candidate queue review。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer review inventory gate:`services.market_intel.mcp_fetch_candidate_queue_writer_review_inventory`、`services.market_intel.mcp_fetch_candidate_queue_writer_review_inventory_gates`、`services.market_intel.mcp_fetch_candidate_queue_writer_review_inventory_sample` 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_review_inventory` 在 writer review handoff 通過後審核 operator read-only candidate queue inventory 摘要,檢查 handoff identity、target table、row count、dedupe keys、review_state、artifact paths、read-only query result、missing/duplicate rows 與 operator confirmations;API/UI 不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不更新 review_state、不做 inventory query、不掛 scheduler,只放行到後續人工 candidate queue review。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer review decision gate:`services.market_intel.mcp_fetch_candidate_queue_writer_review_decision`、`services.market_intel.mcp_fetch_candidate_queue_writer_review_decision_gates`、`services.market_intel.mcp_fetch_candidate_queue_writer_review_decision_sample` 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_review_decision` 在 review inventory 通過後審核 operator candidate queue review decision 摘要,檢查 decision identity、target table、row count、dedupe keys、`needs_review` 現態、允許決策集合、evidence refs、matched row exact-identity/variant/overwrite guard、operator confirmations 與 forbidden API actions;API/UI 不讀 approval token、不執行 CLI、不開 DB、不寫 decision record、不更新 review_state、不寫 match result、不補 queue、不掛 scheduler,只放行到 decision approval / writer preflight 設計。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer review decision approval gate:`services.market_intel.mcp_fetch_candidate_queue_writer_review_decision_approval`、`services.market_intel.mcp_fetch_candidate_queue_writer_review_decision_approval_gates`、`services.market_intel.mcp_fetch_candidate_queue_writer_review_decision_approval_sample` 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_review_decision_approval` 在 review decision 通過後只審核 operator human approval 摘要,確認 decision linkage、approval identity、target table、row count、dedupe keys、`approved_for_writer_preflight` approval result、decision/approval evidence refs、artifact paths、matched row exact-identity/variant/overwrite guard、operator confirmations 與 forbidden API actions;API/UI 不讀 approval token、不執行 CLI、不開 DB、不寫 approval record、不寫 decision record、不更新 review_state、不寫 match result、不補 queue、不掛 scheduler,只放行到後續 writer preflight 設計。此 endpoint 已拆入 `routes.market_intel_mcp_review_routes`,避免 `routes.market_intel_mcp_run_routes` 超過 800 行治理門檻。
|
||||
- 2026-05-31 追加 MCP fetch candidate queue writer review decision approval writer preflight gate:`services.market_intel.mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight`、對應 gates/sample 與 `/api/market_intel/mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight` 在 human approval 通過後只審核 operator writer preflight 摘要,確認 approval linkage、writer_preflight_id、target operation、row count、dedupe keys、approved decision 到 target review_state 的逐列映射、decision/approval/preflight evidence refs、matched row exact-identity/variant/overwrite guard 與 operator boundary;API/UI 不讀 approval token、不執行 CLI、不開 DB、不寫 preflight/approval/decision/match、不更新 review_state、不補 queue、不掛 scheduler,只放行到後續 CLI review / run package 設計。
|
||||
- 2026-06-01 追加 Professional Source Governance gate:`services.market_intel.mcp_professional_source_governance`、對應 gates/sample 與 `/api/market_intel/mcp_professional_source_governance` 將 robots/REP、sitemap/lastmod、JSON-LD / schema.org structured data、canonical URL、rate limit、公開資料邊界、provenance、snapshot hash 與 idempotency key 整理為 source contract。此 gate 只審核 operator source governance 摘要,不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不掛 scheduler;後續 fetch target review 才能引用通過治理的公開來源。
|
||||
- 2026-06-03 追加 Source Governance → Fetch Target bridge:`services.market_intel.mcp_fetch_target_source_governance_review` 與 `/api/market_intel/mcp_fetch_target_source_governance_review` 只交叉審核已通過治理的 source contract 與 MCP Fetch Target Review,要求每個 target `platform_code/source_key` 都命中治理摘要;仍不執行外部 fetch、不讀 robots/sitemap、不開 DB、不寫檔、不執行 CLI、不掛 scheduler。
|
||||
- 2026-05-18 追加 scheduler attach plan preview:`services.market_intel.scheduler_plan` 與 `/api/market_intel/scheduler_plan` 描述未來 `campaign_discovery_daily`、`campaign_product_probe`、`product_match_review_seed` 三個 job 的 cadence、gate、fallback 與安全邊界。此階段不註冊 scheduler job、不啟動 crawler、不連外、不寫 DB;排程掛載必須等 migration、seed、MCP fetch gate、manual sample 與人工批准全過。
|
||||
- 2026-05-18 追加 match review plan preview:`services.market_intel.match_review_plan` 與 `/api/market_intel/match_review_plan` 定義商品比對訊號、分數門檻、`needs_review → confirmed/rejected` HITL 流程與安全邊界。此階段不建立 review queue、不自動 confirmed、不寫 `market_product_matches`、不呼叫 MCP;價格只能作為輔助訊號,不能單獨決定同品比對。
|
||||
- 2026-05-18 追加 opportunity plan preview:`services.market_intel.opportunity_plan` 與 `/api/market_intel/opportunity_plan` 定義競品低價威脅、促銷缺口、深折重疊、活動即將結束四類規則與分級策略。此階段不建立 opportunity queue、不派送 Telegram、不產生 AI 摘要、不寫 DB;高風險項必須先有 confirmed match 與 DB evidence 才能升級。
|
||||
- 2026-05-18 追加 opportunity scoring plan preview:`services.market_intel.opportunity_scoring` 與 `/api/market_intel/opportunity_scoring_plan` 定義價差、折扣深度、比對可信度、活動急迫性與資料新鮮度五個 scoring dimensions、分級門檻、evidence tables 與人工升級順序。此階段不計分、不產生 sample scores、不建立 scoring job、不寫 DB、不派送 Telegram、不產生 AI 摘要。
|
||||
- 2026-05-18 追加 opportunity evidence plan preview:`services.market_intel.opportunity_evidence` 與 `/api/market_intel/opportunity_evidence_plan` 定義 scoring / alert / AI 摘要必須攜帶的 campaign、market product、match review、MOMO reference、scoring trace 五段 evidence bundle contract。此階段不查 DB、不產生 sample evidence、不建立 alert candidate、不派送 Telegram、不產生 AI 摘要。
|
||||
- 2026-05-18 追加 opportunity alert plan preview:`services.market_intel.opportunity_alerts` 與 `/api/market_intel/opportunity_alert_plan` 定義人工審核、每日摘要、Telegram 候選、AI 摘要候選四種 channel 的最低門檻、去重節流、payload contract 與操作順序。此階段不建立 alert queue、不派送 Telegram、不呼叫 LLM、不寫 DB;所有告警必須先有 evidence bundle、dedupe key 與人工批准。
|
||||
- 2026-05-18 追加 opportunity alert review preview:`/api/market_intel/opportunity_alert_plan` 擴充人工審核狀態與操作,定義 `draft → needs_review → approved_for_digest / approved_for_telegram / rejected / deferred` 流程、審核理由、審核者身分與派送前二次 gate。此階段不建立 review queue、不執行審核動作、不寫 approval record、不派送 Telegram、不呼叫 LLM。
|
||||
- 2026-05-18 追加 deployment readiness modularization:將 `/api/market_intel/deployment_readiness` 的大型 app-only release gate 組裝邏輯由 `services.market_intel.service` 拆至 `services.market_intel.deployment_readiness`,主服務保留薄入口,避免後續 crawler / MCP / 審核功能推進時超過 800 行治理線;行為仍維持 preview-only,不執行 git、部署、SSH、migration 或 DB write。
|
||||
- 2026-05-18 追加 alert review queue contract:`/api/market_intel/opportunity_alert_plan` 補上 `market_alert_review_queue` 的 preview contract、required / audit / forbidden fields、priority lanes 與索引規劃。此階段只定義資料契約,不建立 review table、不寫 queue contract、不執行審核、不派送 Telegram、不呼叫 LLM。
|
||||
- 2026-05-18 追加 alert review queue migration blueprint:`market_alert_review_queue` 納入 `database/market_intel_models.py`、`migrations/032_market_intel_core_schema.sql` 與 migration blueprint,補齊 additive CREATE TABLE / index / grant / rollback draft。此階段仍不執行 migration、不連 DB、不建立 review queue、不寫入審核資料。
|
||||
- 2026-05-18 追加 migration apply drill preview:`services.market_intel.migration_drill` 與 `/api/market_intel/migration_apply_drill` 集中正式 migration 前的只讀 schema probe、platform seed diff、人工套用清單、post-apply smoke、回滾演練與風險清單。預設 `execute=false` 不連 DB;人工 smoke 可用 `execute=true` 觸發只讀 catalog / seed diff probe,但仍不執行 psql、不跑 rollback、不寫 DB、不重啟容器、不掛 scheduler。
|
||||
- 2026-05-18 追加 migration catalog review preview:`services.market_intel.migration_catalog_review` 與 `/api/market_intel/migration_catalog_review` 將 schema_db_probe 與 platform_seed_db_diff 的只讀結果歸納為 `planned_no_probe` / `not_applied` / `partial_schema` / `already_applied` / `probe_error`,並輸出 risk level、apply path、finding 與下一步。預設 `execute=false` 不連 DB;`execute=true` 仍只查 catalog / seed diff,不執行 psql、不寫 DB、不跑 rollback、不掛 scheduler。
|
||||
- 2026-05-18 追加 migration live smoke preview:`services.market_intel.migration_live_smoke` 與 `/api/market_intel/migration_live_smoke` 將 `execute=true` 的正式 DB 只讀探測整理成 smoke result,例如 `passed_not_applied_seed_table_missing`、`attention_partial_schema`、`passed_already_applied` 或 `failed_catalog_probe_error`。UI 預設仍只呼叫 `execute=false`;人工 smoke 可用 `execute=true` 只讀查 catalog,不執行 migration、不寫 DB、不跑 rollback、不掛 scheduler。
|
||||
- 2026-05-18 追加 live DB inventory preview:`services.market_intel.live_db_inventory` 與 `/api/market_intel/live_db_inventory` 預設 `execute=false` 只回 planned,不連 DB;人工 smoke 可用 `execute=true` 對 `market_*` tables 執行只讀 count / group by,產生平台、活動狀態、商品活躍度、比對狀態、告警審核佇列與 crawler run 基準總覽。此探針不建立 ORM session、不寫 DB、不執行 migration、不連外、不掛 scheduler。
|
||||
- 2026-05-19 追加 manual sample fetch plan preview:`services.market_intel.manual_sample_plan` 與 `/api/market_intel/manual_sample_plan` 定義第一次人工 sample fetch 的平台順序、每平台 1 個公開入口、MCP fetch gate、正式 DB 庫存基準、操作員步驟與備援。此階段只產生計畫,不抓外部網站、不寫 DB、不建立 crawler run、不掛 scheduler、不繞反爬。
|
||||
- 2026-05-19 追加 manual sample acceptance contract:`services.market_intel.manual_sample_acceptance` 與 `/api/market_intel/manual_sample_acceptance` 定義 sample fetch 結果回來後的必要欄位、診斷欄位、驗收門檻、拒收條件、人工決策與升級順序。此階段不載入 sample result、不抓外部網站、不允許候選導入、不寫 DB、不掛 scheduler。
|
||||
- 2026-05-19 追加 manual sample result review:`services.market_intel.manual_sample_review` 與 `/api/market_intel/manual_sample_review` 以純函式評估操作員提供的 sample result 是否通過 Phase 48 驗收門檻並可進候選預覽。API/UI 預設不載入 sample result,不抓外站、不存檔、不寫 DB、不允許候選導入、不掛 scheduler;即使審核通過也只開 candidate preview gate,正式 market_* 寫入仍需後續獨立批准。
|
||||
- 2026-05-19 追加 manual sample review evaluate:`/api/market_intel/manual_sample_review/evaluate` 提供 POST-only 即時審核入口,操作員可貼入單筆 sample result JSON 取得 PASS/BLOCK 與 candidate preview readiness。此入口不保存 payload、不回吐完整 HTML、不連外、不寫 DB、不建立候選活動、不允許候選導入、不掛 scheduler;invalid JSON 只回診斷錯誤。
|
||||
- 2026-05-19 追加 manual sample candidate handoff:`/api/market_intel/manual_sample_review/candidate_handoff` 將已通過審核的 sample result 轉成只讀候選活動 handoff payload,包含 candidate key、平台、來源、URL、信心分級、排序與人工審核狀態。此入口保持 CSRF 保護,不保存 handoff、不建立 review queue、不寫 market_*、不允許候選導入、不掛 scheduler。
|
||||
- 2026-05-19 追加 manual sample candidate queue draft:`services.market_intel.manual_sample_candidate_queue` 與 `/api/market_intel/manual_sample_review/candidate_queue_draft` 將 handoff 候選轉成只讀人工審核 queue draft,包含 queue item key、review priority、review state 與 approval_required。此入口保持 CSRF 保護,不建立正式 queue、不保存草案、不寫 market_*、不自動核准候選、不掛 scheduler。
|
||||
- 2026-05-19 追加 manual sample candidate queue approval:`/api/market_intel/manual_sample_review/candidate_queue_approval` 將 queue draft 對齊既有 `market_alert_review_queue` 契約,輸出 row preview、必填欄位 gate、寫入 flags、備份與人工批准 gate。此入口保持 CSRF 保護,不建立 approval record、不寫 review queue、不開 DB transaction、不掛 scheduler。
|
||||
- 2026-05-19 追加 manual sample candidate queue transaction:`/api/market_intel/manual_sample_review/candidate_queue_transaction` 將 queue row preview 轉成 idempotent insert statement preview、payload hash、runtime order 與 rollback plan。此入口保持 CSRF 保護,不開 DB connection、不開 transaction、不 commit、不建立 approval record、不寫 `market_alert_review_queue`。
|
||||
- 2026-05-19 追加 candidate queue writer CLI gate:`services.market_intel.candidate_queue_writer_cli`、`scripts/market_intel_candidate_queue_writer.py` 與 `/api/market_intel/manual_sample_review/candidate_queue_writer_status` 定義 `MARKET_INTEL_QUEUE_WRITE_APPROVAL` 一次性 token、execute/apply flags、備份、migration smoke 與 rollback gate。此階段只回 writer status,不開 DB connection、不啟用實際 writer、不寫 `market_alert_review_queue`、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue writer preflight:`services.market_intel.candidate_queue_writer_preflight` 與 `/api/market_intel/manual_sample_review/candidate_queue_writer_preflight` 檢查 transaction preview payload key 到 `market_alert_review_queue` 欄位映射、缺欄與 dedupe unique index。UI 預設 `execute=false` 不連 DB;CLI 明確 `--read-only-preflight` 時也只查 catalog,不寫 DB、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue writer CLI transaction:`scripts/market_intel_candidate_queue_writer.py` 在 CLI-only 情境支援受控 idempotent insert transaction,必須同時通過 transaction payload、read-only preflight、`--execute`、`--apply-real-write`、一次性 approval token、備份確認與 migration live smoke。API/UI 仍不得讀取 approval token、不得開 DB connection、不得寫 `market_alert_review_queue`、不得掛 scheduler;部署 smoke 不執行正式 DB 寫入。
|
||||
- 2026-05-19 追加 candidate queue writer post-write smoke:`services.market_intel.candidate_queue_writer_postwrite_smoke` 與 `/api/market_intel/manual_sample_review/candidate_queue_writer_postwrite_smoke` 依 transaction preview dedupe key 只讀查詢 `market_alert_review_queue`,供 CLI 真寫入後確認 row 是否落地。UI 預設 `execute=false` 不連 DB;人工 smoke 即使 `execute=true` 也只讀查詢,不寫 DB、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue writer operator drill:`services.market_intel.candidate_queue_writer_operator_drill` 與 `/api/market_intel/manual_sample_review/candidate_queue_writer_operator_drill` 組裝 reviewed sample、備份、read-only preflight、CLI writer、post-write smoke 的操作員順序與 gate。此 drill 只輸出可稽核操作計畫;API/UI 不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue writer run package:`services.market_intel.candidate_queue_writer_run_package` 與 `/api/market_intel/manual_sample_review/candidate_queue_writer_run_package` 整理正式 CLI 小流量寫入前的 payload manifest、required artifacts、command bundle、operator signoff 與 rollback plan。此 package 只輸出可稽核證據包預覽;API/UI 不產檔、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue writer run readiness:`services.market_intel.candidate_queue_writer_run_readiness` 與 `/api/market_intel/manual_sample_review/candidate_queue_writer_run_readiness` 檢查正式 CLI 小流量寫入前的 reviewed sample 路徑、備份路徑、preflight 輸出、migration live smoke、shell-only token acknowledgement 與禁止 token 進 API。此 readiness 只輸出操作員證據缺口;API/UI 不產檔、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue writer run receipt:`services.market_intel.candidate_queue_writer_run_receipt` 與 `/api/market_intel/manual_sample_review/candidate_queue_writer_run_receipt` 審核 CLI 小流量寫入後的 writer output、post-write smoke、dedupe key 一致性、artifact 路徑與 token 外洩風險。此 receipt 只輸出安全摘要;API/UI 不回吐 receipt 原文、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue writer run closeout:`services.market_intel.candidate_queue_writer_run_closeout` 與 `/api/market_intel/manual_sample_review/candidate_queue_writer_run_closeout` 在 receipt 通過後整理 closeout gate、人工確認與下一階段 promotion 摘要。此 closeout 只允許放行到人工 queue review / read-only inventory;API/UI 不回吐原始 receipt、不讀 approval token、不執行 CLI、不連 DB、不寫 queue、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review handoff:`services.market_intel.candidate_queue_review_handoff` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_handoff` 將 closeout 後的 expected dedupe key、review contract、人工操作順序與 forbidden API actions 整理為交接包。此 handoff 不查 DB、不更新 `review_state`、不補寫 missing queue row、不讀 approval token、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review inventory:`services.market_intel.candidate_queue_review_inventory` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_inventory` 將 handoff、post-write smoke 與 live DB inventory 串成只讀人工審核庫存檢查。預設不連 DB;人工明確要求只讀查詢時仍不更新 `review_state`、不補寫 queue row、不讀 approval token、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review decision:`services.market_intel.candidate_queue_review_decision` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision` 將通過 inventory 的 queue row 整理成人工決策草案,限制 next state 為 `confirmed` / `rejected` / `deferred`。此階段不更新 `review_state`、不寫 decision record、不讀 approval token、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review decision approval:`services.market_intel.candidate_queue_review_decision_approval` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision_approval` 檢查人工決策草案是否可進入下一個 CLI-only transaction preview。此階段不更新 `review_state`、不寫 decision record、不建立 approval record、不讀 approval token、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review decision transaction:`services.market_intel.candidate_queue_review_decision_transaction` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision_transaction` 將已批准的人工決策整理成 `review_state` update statement preview、payload hash、runtime order 與 rollback plan。此階段不連 DB、不開 transaction、不 commit、不更新 `review_state`、不讀 approval token、不執行 CLI、不掛 scheduler;真正更新只允許後續人工 shell/CLI 寫入窗口。
|
||||
- 2026-05-19 追加 candidate queue review decision writer CLI gate:`services.market_intel.candidate_queue_review_decision_writer_cli`、`scripts/market_intel_review_decision_writer.py` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_status` 先建立 review_state writer 的 shell-only gate、一次性 token env var、command bundle 與 rollback plan。此階段 writer implementation 保持 disabled;API/UI 不讀 approval token、不執行 CLI、不連 DB、不開 transaction、不 commit、不更新 `review_state`、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review decision writer preflight:`services.market_intel.candidate_queue_review_decision_writer_preflight` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_preflight` 檢查 writer status、review_state update payload、狀態轉換與 token 外洩風險。API/UI 即使收到 `execute=true` 或 `apply_real_write=true` 也只回 blocked preview,不連 DB、不執行 CLI、不更新 `review_state`、不 commit、不讀 approval token、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review decision writer post-write smoke:`services.market_intel.candidate_queue_review_decision_writer_postwrite_smoke` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_postwrite_smoke` 依 review_state transaction preview 的 dedupe key 只讀查詢 `market_alert_review_queue`,確認人工 CLI 更新後的 `review_state` 是否符合預期。UI 預設 `execute=false` 不連 DB;人工 smoke 即使 `execute=true` 也只讀查詢,不更新 `review_state`、不 commit、不讀 approval token、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review decision writer operator drill:`services.market_intel.candidate_queue_review_decision_writer_operator_drill` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_operator_drill` 整理 review_state CLI 更新前後的操作員順序、preflight、post-write smoke、gate 與 rollback plan。此 drill 只輸出可稽核操作計畫;API/UI 不讀 approval token、不執行 CLI、不連 DB、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review decision writer run package:`services.market_intel.candidate_queue_review_decision_writer_run_package` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_package` 將 review_state transaction、preflight、operator drill、writer gate、post-write smoke、必要 artifact 與 rollback plan 組成正式 CLI 更新前的可稽核 package。此 package 只輸出預覽;API/UI 不寫檔、不讀 approval token、不執行 CLI、不連 DB、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review decision writer run readiness:`services.market_intel.candidate_queue_review_decision_writer_run_readiness` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_readiness` 檢查 review_state CLI 更新前的 transaction JSON、備份、preflight、shell-only token 與 post-write smoke 計畫是否齊備。此 readiness 只輸出操作員 gate 預覽;API/UI 不寫檔、不讀 approval token、不執行 CLI、不連 DB、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review decision writer run receipt:`services.market_intel.candidate_queue_review_decision_writer_run_receipt` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_receipt` 審核 review_state CLI 更新後的 writer output、post-write smoke、dedupe key 一致性、artifact 路徑與 token 外洩風險。此 receipt 只輸出安全摘要;API/UI 不回吐 receipt 原文、不讀 approval token、不執行 CLI、不連 DB、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review decision writer run closeout:`services.market_intel.candidate_queue_review_decision_writer_run_closeout` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_closeout` 在 review_state receipt 通過後整理 closeout gate、操作員 closeout artifact、post-closeout read-only inventory 確認與 promotion 摘要。此 closeout 只允許放行到後續人工/只讀檢查;API/UI 不回吐 receipt 原文、不讀 approval token、不執行 CLI、不連 DB、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review decision post-closeout inventory:`services.market_intel.candidate_queue_review_decision_post_closeout_inventory`、`routes.market_intel_review_post_routes` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_decision_post_closeout_inventory` 在 review_state closeout 後整理 post-write smoke、live inventory、dedupe key 與 review_state 結果。此 inventory 只允許只讀驗證;API/UI 不讀 approval token、不執行 CLI、不更新 `review_state`、不寫 DB、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review completion archive:`services.market_intel.candidate_queue_review_completion_archive` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_completion_archive` 在 post-closeout inventory 後整理 receipt、closeout、inventory、dedupe key、review_state row snapshot 與 artifact path manifest。此 archive gate 只輸出封存預覽;API/UI 不寫檔、不讀 approval token、不執行 CLI、不更新 `review_state`、不寫 DB、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review archive summary:`services.market_intel.candidate_queue_review_archive_summary` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_archive_summary` 在 review completion archive 後整理可供摘要/報表審核的結構化輸入。此 summary gate 不產生 AI 摘要;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 approval token、不執行 CLI、不更新 `review_state`、不寫 DB、不 commit、不掛 scheduler。
|
||||
- 2026-05-19 追加 candidate queue review AI summary preflight:`services.market_intel.candidate_queue_review_ai_summary_preflight` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_preflight` 在 archive summary 後檢查未來 AI 摘要前置條件、Ollama-first 三主機級聯與 Gemini 備援邊界。此 preflight 不產生 AI 摘要;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 approval token、不執行 CLI、不更新 `review_state`、不寫 DB、不 commit、不掛 scheduler;188 不可作為 Ollama 節點。
|
||||
- 2026-05-19 追加 candidate queue review AI summary run package:`services.market_intel.candidate_queue_review_ai_summary_run_package` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_run_package` 在 AI summary preflight 後整理手動 Ollama 摘要任務包、prompt contract、輸出 schema 與 artifact path contract。此 package gate 不產生 AI 摘要;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 approval token、不執行 CLI、不更新 `review_state`、不寫 DB、不 commit、不掛 scheduler;Gemini 僅能作為 Ollama cascade 全失敗後的備援。
|
||||
- 2026-05-19 追加 candidate queue review AI summary output receipt:`services.market_intel.candidate_queue_review_ai_summary_output_receipt` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_output_receipt` 在 run package 後驗收人工 Ollama 摘要輸出的 schema、`evidence_refs` 與 `model_route`。此 receipt gate 不產生 AI 摘要;API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 approval token、不執行 CLI、不更新 `review_state`、不寫 DB、不 commit、不掛 scheduler;摘要持久化與 Telegram 派送必須另開後續 gate。
|
||||
- 2026-05-19 追加 candidate queue review AI summary persistence preflight:`services.market_intel.candidate_queue_review_ai_summary_persistence_preflight` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_preflight` 在 output receipt 後整理未來 CLI-only `market_alert_review_queue.metadata_json.ai_summary_review` persistence contract、payload hash 與 metadata patch preview。此 preflight 不產生 transaction、不寫 summary record、不寫 `metadata_json`、不呼叫 LLM、不派送 Telegram、不讀 approval token、不執行 CLI、不更新 `review_state`、不寫 DB、不 commit、不掛 scheduler;真正持久化與 Telegram dispatch 必須另開後續 gate。
|
||||
- 2026-05-19 追加 candidate queue review AI summary persistence transaction:`services.market_intel.candidate_queue_review_ai_summary_persistence_transaction` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_transaction` 在 persistence preflight 後產生未來 CLI-only `metadata_json` UPDATE statement preview、parameter preview 與 rollback plan。此 transaction gate 不開 DB、不執行 SQL、不寫 transaction、不寫 summary record、不寫 `metadata_json`、不讀 approval token、不執行 CLI、不更新 `review_state`、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;真正寫入必須另開 CLI writer gate 與 post-write smoke。
|
||||
- 2026-05-19 追加 candidate queue review AI summary persistence writer preflight:`services.market_intel.candidate_queue_review_ai_summary_persistence_writer_preflight` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_writer_preflight` 在 transaction preview 後檢查 CLI-only writer contract、`metadata_json` backup requirement、post-write smoke requirement 與 artifact path gate。此 writer preflight 不開 DB、不執行 SQL、不寫 preflight、不寫 summary record、不寫 `metadata_json`、不讀 approval token、不執行 CLI、不更新 `review_state`、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;真正寫入必須另開 CLI run package、operator readiness、receipt 與 post-write smoke。
|
||||
- 2026-05-19 追加 candidate queue review AI summary persistence run package:`services.market_intel.candidate_queue_review_ai_summary_persistence_run_package` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_package` 在 writer preflight 後整理 payload manifest、CLI command bundle、required artifacts、operator signoff 與 rollback plan。此 run package 不開 DB、不執行 SQL、不寫 run package、不寫 summary record、不寫 `metadata_json`、不讀 approval token、不執行 CLI、不更新 `review_state`、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;真正寫入必須另開 operator readiness、CLI receipt、post-write smoke 與 closeout。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence run readiness:`services.market_intel.candidate_queue_review_ai_summary_persistence_run_readiness` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_readiness` 在 run package 後檢查操作員 artifact path、metadata_json backup、read-only preflight、shell-only token acknowledgement 與 post-write smoke 計畫。此 readiness 不開 DB、不執行 SQL、不寫 readiness artifact、不寫 summary record、不寫 `metadata_json`、不讀 approval token、不執行 CLI、不更新 `review_state`、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler;真正寫入必須由後續人工 CLI 與 receipt/post-write smoke gate 驗收。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence run receipt:`services.market_intel.candidate_queue_review_ai_summary_persistence_run_receipt` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_receipt` 審核 metadata_json CLI 寫入後的 writer output、post-write smoke、dedupe key、summary payload hash、artifact path 與 token 外洩風險。此 receipt 只輸出安全摘要;API/UI 不回吐 receipt 原文、不讀 approval token、不執行 CLI、不開 DB、不寫 `metadata_json`、不更新 `review_state`、不派送 Telegram、不 commit、不掛 scheduler。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence run closeout:`services.market_intel.candidate_queue_review_ai_summary_persistence_run_closeout` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_closeout` 在 receipt 通過後整理 closeout gate、操作員 closeout artifact、Telegram dispatch separate-gate 確認與 promotion 摘要。此 closeout 只允許放行到後續 Telegram dispatch gate;API/UI 不回吐 receipt 原文、不讀 approval token、不執行 CLI、不開 DB、不寫 `metadata_json`、不更新 `review_state`、不派送 Telegram、不呼叫 LLM、不 commit、不掛 scheduler。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch gate:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate` 在 closeout 後整理 Telegram message contract、channel label、artifact path、token 外洩檢查與下一階段 run package promotion。此 gate 只允許放行到後續 Telegram dispatch run package;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch run package:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_package` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_package` 在 dispatch gate 後整理 message contract snapshot、command plan、artifact checklist 與下一階段 readiness promotion。此 run package 只允許放行到後續 Telegram dispatch run readiness;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch run readiness:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_readiness` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_readiness` 在 dispatch run package 後檢查 message preview、run package、dispatch receipt artifact path、manual command review、Telegram token shell-only 與 no-side-effect 確認。此 readiness 只允許放行到人工 Telegram dispatch 後的 receipt review;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不開 DB、不寫檔、不派送 Telegram、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch run receipt:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_receipt` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_receipt` 審核人工 Telegram 派送後貼回的 message id、channel、summary hash、receipt artifact 與 token 外洩風險。此 receipt 只允許放行到後續 Telegram dispatch closeout;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補發或重送 Telegram、不開 DB、不寫檔、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch closeout:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_closeout` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_closeout` 在人工 Telegram receipt 通過後整理 closeout artifact、receipt archive、message visibility、duplicate dispatch 與 post-closeout monitoring gate。此 closeout 只允許放行到後續封存/報表 gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補發或重送 Telegram、不開 DB、不寫檔、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch archive:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive` 在 closeout 通過後整理 message id、channel、summary hash、receipt/closeout/message preview/archive artifact path 與 duplicate/monitoring audit manifest。此 archive 只輸出封存預覽並放行到後續 archive summary / 報表輸入 gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補發或重送 Telegram、不開 DB、不寫檔、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch archive summary:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive_summary` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive_summary` 在 archive 通過後整理 message identity、dispatch audit、artifact manifest 與後續 report input 所需 sections。此 archive summary 只準備報表/summary input;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補發或重送 Telegram、不開 DB、不寫檔、不更新 `review_state`、不 commit、不掛 scheduler,後續報表輸入必須另開 gate。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch report input:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_input`、`routes.market_intel_review_report_routes` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_input` 在 archive summary 通過後整理 report input sections、report contract、message evidence 與 dispatch audit traceability。此 report input 只準備後續報表 run package;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補發或重送 Telegram、不開 DB、不寫檔、不產報表、不更新 `review_state`、不 commit、不掛 scheduler,真正報表產製必須另開 gate。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch report run package:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_package`、`routes.market_intel_review_report_routes` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_package` 在 report input 通過後整理 run package contract、evidence refs、package sections 與後續 report run readiness gate。此 run package 只準備報表執行審核包;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補發或重送 Telegram、不開 DB、不寫檔、不產報表、不更新 `review_state`、不 commit、不掛 scheduler,真正報表產製必須另開 readiness gate。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch report run readiness:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_readiness`、`routes.market_intel_review_report_routes` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_readiness` 在 report run package 通過後整理 report generation readiness manifest、manual report command boundary、artifact path gate 與後續 report run receipt gate。此 run readiness 只檢查可否進入後續人工/獨立 job 報表產製;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補發或重送 Telegram、不開 DB、不寫檔、不產報表、不更新 `review_state`、不 commit、不掛 scheduler,真正產出結果必須另開 receipt gate。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch report run receipt:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_receipt`、`routes.market_intel_review_report_routes` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_receipt` 在 report run readiness 通過後審核人工/獨立 job 產出的 report receipt、report artifact path/hash、必要章節、summary hash 與 runtime boundary。此 receipt 只允許放行到後續 market intel report closeout;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不補發或重送 Telegram、不開 DB、不寫檔、不更新 `review_state`、不 commit、不掛 scheduler。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch report closeout:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_closeout`、`routes.market_intel_review_report_routes` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_closeout` 在 report run receipt 通過後審核 closeout artifact、receipt/report artifact path、hash 與章節覆核、archive separate gate 與 runtime boundary。此 closeout 只允許放行到後續 market intel report archive;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不補發或重送 Telegram、不開 DB、不寫檔、不更新 `review_state`、不 commit、不掛 scheduler,後續 report archive 必須另開 gate。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch report archive:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive`、`routes.market_intel_review_report_routes` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive` 在 report closeout 通過後審核 archive/closeout/receipt/report output artifact path、hash 與章節、archive manifest、retention policy 與後續 archive summary separate gate。此 archive 只允許放行到後續 market intel report archive summary;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不補發或重送 Telegram、不開 DB、不寫檔、不更新 `review_state`、不 commit、不掛 scheduler,後續 archive summary 必須另開 gate。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch report archive summary:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive_summary`、`routes.market_intel_review_report_routes` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive_summary` 在 report archive 通過後整理 report identity、archive traceability、integrity review 與 runtime safety sections,僅放行到後續 report catalog handoff gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不補發或重送 Telegram、不開 DB、不寫檔、不更新 `review_state`、不 commit、不掛 scheduler,後續 catalog handoff 必須另開 gate。
|
||||
- 2026-05-20 追加 candidate queue review AI summary persistence Telegram dispatch report catalog handoff:`services.market_intel.candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_handoff`、`routes.market_intel_review_report_routes` 與 `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_handoff` 在 report archive summary 通過後整理 catalog identity、artifact manifest、section keys 與 hash traceability,僅放行到後續 report catalog index gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不補發或重送 Telegram、不開 DB、不寫 catalog record、不更新 `review_state`、不 commit、不掛 scheduler,後續 catalog index / write preflight 必須另開 gate。
|
||||
|
||||
### Phase 4:Coupang / Shopee Adapter
|
||||
|
||||
|
||||
45
docs/adr/ADR-037-webcrumbs-shared-ui-runtime.md
Normal file
45
docs/adr/ADR-037-webcrumbs-shared-ui-runtime.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# ADR-037: Webcrumbs 共用 UI Runtime 接入
|
||||
|
||||
Status: Accepted
|
||||
Date: 2026-05-31
|
||||
|
||||
## Context
|
||||
|
||||
momo-pro 的前端已進入 V2/V3 視覺治理階段,後續會需要更穩定的跨頁 UI 元件、microfrontend/plugin loader 與跨專案視覺實驗入口。Webcrumbs 可作為共用 UI runtime,但若各專案各自引用官方 CDN、使用 `@latest`,或把上游 repo 整包放進 momo-pro,會造成供應鏈、版本漂移、授權與生產可控性風險。
|
||||
|
||||
本專案目前仍以 Flask/Jinja 為正式 runtime,不能因導入外部 UI runtime 而繞過既有登入、CSRF、health check、部署與真實資料不退化紅線。
|
||||
|
||||
## Decision
|
||||
|
||||
Webcrumbs 在 momo-pro 中只作為「自架、固定版本、可診斷」的共用 UI runtime/plugin loader,不作為主前端框架替代品。
|
||||
|
||||
採用以下規則:
|
||||
|
||||
1. runtime 由 `WEBCRUMBS_*` 環境變數設定,正式頁面預設指向 momo-pro 同源 `/webcrumbs-assets/loader/webcrumbs-compatible-loader.js`。
|
||||
2. `/webcrumbs-assets/` 只代理 allowlist prefix:`loader/`、`plugins/`、`demo/`,上游預設為 188 user-space Shared UI Hub `http://192.168.0.188:18088`。
|
||||
3. `ewoooc_base.html` 只在 `WEBCRUMBS_ENABLED=true` 且 runtime URL 通過 http(s) 或同源絕對路徑檢查時輸出 script。
|
||||
4. 新增 `/webcrumbs` 診斷入口,顯示 runtime URL、版本、plugin base 與 asset upstream 狀態。
|
||||
5. Webcrumbs plugin 必須使用自架且版本化 URI,例如 `/webcrumbs-assets/plugins/<namespace>/<version>/`。
|
||||
6. 禁止在生產使用官方 `@latest`、`app.webcrumbs.ai` 或未固定版本的官方 CDN。
|
||||
7. 不把 Webcrumbs 上游 repo 整包納入 momo-pro 主服務;若修改 runtime 本體,需另外處理 AGPL-3.0 授權義務。
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### A. 每個頁面自行引用官方 Webcrumbs CDN
|
||||
|
||||
拒絕。版本不可控,且容易讓正式頁面受外部服務可用性與上游 breaking change 影響。
|
||||
|
||||
### B. 把 Webcrumbs repo vendoring 到 momo-pro
|
||||
|
||||
拒絕。這會增加主服務體積、授權治理與升級成本,也會讓 UI runtime 與業務後端部署生命週期綁死。
|
||||
|
||||
### C. 暫不接任何 runtime,只保留純 Jinja/CSS
|
||||
|
||||
保留作為 fallback,但不足以支援後續跨專案 microfrontend/plugin 化需求。導入 Webcrumbs 時仍維持現有 Jinja 頁面為主,不強制改寫。
|
||||
|
||||
## Consequences
|
||||
|
||||
- momo-pro 可在不替換 Flask/Jinja 的前提下,逐步嵌入自架 UI plugin。
|
||||
- 生產部署需同步管理 `WEBCRUMBS_*` 變數與 Shared UI Hub asset upstream。
|
||||
- 後續任何 Webcrumbs plugin 上線前,仍需通過真實資料、權限、RWD、效能與 fallback 檢查。
|
||||
- 若 runtime URL 無效,系統只會跳過 script 載入並在 config warning / `/webcrumbs` 顯示狀態,不應阻斷主站。
|
||||
@@ -58,6 +58,7 @@
|
||||
| [034](ADR-034-dynamic-model-router.md) | Caller × Context 動態 Model Router(短文 gemma3 / 複雜 SKU qwen3:14b / 重構 coder:32b) | Accepted | 2026-05-04 |
|
||||
| [035](ADR-035-cross-platform-market-campaign-intelligence.md) | 跨平台市場活動情報系統 | Accepted | 2026-05-06 |
|
||||
| [036](ADR-036-fastapi-strangler-not-frontend-prerequisite.md) | FastAPI Strangler Migration,不作為前端 V3 前置條件 | Accepted | 2026-05-12 |
|
||||
| [037](ADR-037-webcrumbs-shared-ui-runtime.md) | Webcrumbs 共用 UI Runtime 接入 | Accepted | 2026-05-31 |
|
||||
|
||||
## 規範
|
||||
|
||||
|
||||
53
docs/guides/browse_sh_crawler_playbook.md
Normal file
53
docs/guides/browse_sh_crawler_playbook.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Browse.sh Crawler Playbook
|
||||
|
||||
> Scope: MOMO / PChome 動態頁診斷、selector 探勘、XHR/network trace。正式價格資料仍以既有 Python crawler、PChome API 與資料庫閉環為準。
|
||||
|
||||
## 評估結論
|
||||
|
||||
- `browse.sh` 是 Browserbase 提供的 browser CLI,主打 open web catalog、browser primitives、debugging、cloud sessions 與 network/console tail。
|
||||
- 官方安裝入口為 `npm install -g browse`,CLI 名稱是 `browse`。
|
||||
- 對本專案最有價值的地方不是取代爬蟲,而是當 MOMO/PChome HTML 或前端 XHR 改版時,快速看 selector、console、network 與可重放 skill。
|
||||
- 本機目前 Node 16 因 `icu4c` 動態庫缺失無法啟動,不能在本機直接安裝或執行 `browse`。導入方式先採 optional wrapper,不影響 production scheduler。
|
||||
|
||||
## 使用邊界
|
||||
|
||||
- 只允許做 read-only 診斷,不登入、不下單、不加入購物車、不寫第三方狀態。
|
||||
- 不把 `browse` 放進排程主路徑;若未來要排程化,需另開 ADR 與 feature flag。
|
||||
- 不把 `browse` 輸出直接寫成正式價格;所有正式比價仍需經 `services/pchome_crawler.py`、`services/momo_crawler.py`、`services/marketplace_product_matcher.py` 與 `competitor_match_attempts` 診斷。
|
||||
- Cloud sessions 可能需要 Browserbase 帳號與憑證;憑證不可寫入 repo。
|
||||
|
||||
## 本地檢查
|
||||
|
||||
```bash
|
||||
python scripts/tools/browse_sh_probe.py
|
||||
```
|
||||
|
||||
可用時會輸出 `available=true` 與版本;不可用時會輸出原因。若 `browse` 不在 PATH,可用:
|
||||
|
||||
```bash
|
||||
BROWSE_SH_CLI=/path/to/browse python scripts/tools/browse_sh_probe.py
|
||||
```
|
||||
|
||||
## 診斷流程
|
||||
|
||||
1. 先用既有 crawler/API 重現問題,保存失敗 SKU、搜尋詞、候選網址與 matcher diagnostics。
|
||||
2. 使用 `browse` 開同一頁,查看搜尋結果 DOM、network 與 console。
|
||||
3. 若找到穩定 XHR/API,優先回補到 Python crawler;若只能走 DOM,才更新 selector。
|
||||
4. 新增或更新單元測試,至少覆蓋搜尋詞、候選解析與 matcher hard veto。
|
||||
5. 只在測試通過後部署 app/scheduler/bot;不得重建或重啟 `momo-db`。
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
python scripts/tools/browse_sh_probe.py -- --version
|
||||
python scripts/tools/browse_sh_probe.py -- skills list
|
||||
python scripts/tools/browse_sh_probe.py -- open "https://24h.pchome.com.tw/"
|
||||
python scripts/tools/browse_sh_probe.py -- network --tail
|
||||
python scripts/tools/browse_sh_probe.py -- screenshot
|
||||
```
|
||||
|
||||
## MOMO/PChome 導入策略
|
||||
|
||||
- PChome:目前已有搜尋 API 與商品 API,`browse` 只用於確認 API 參數、分頁行為、前端是否切新 endpoint。
|
||||
- MOMO:若既有 BeautifulSoup selector 失效,先用 `browse` 找出前端實際 XHR;找到 API 時優先改成 structured API parser。
|
||||
- Matcher:`browse` 只提供候選證據;是否為同款仍由 `marketplace_product_matcher.score_marketplace_match()` 決定。
|
||||
@@ -19,6 +19,21 @@ scp -o ProxyJump=wooo@192.168.0.110 app.py ollama@192.168.0.188:/home/ollama/mom
|
||||
scp -o ProxyJump=wooo@192.168.0.110 -r services/ ollama@192.168.0.188:/home/ollama/momo-pro/
|
||||
```
|
||||
|
||||
若部署時在 110 → 188 的內層 `scp` 遇到 `Host key verification failed`,先在 110 修正 `known_hosts`,不要用 `StrictHostKeyChecking=no` 硬跳過:
|
||||
```bash
|
||||
ssh wooo@192.168.0.110 \
|
||||
"ssh-keygen -R 192.168.0.188 && ssh-keyscan -H 192.168.0.188 >> ~/.ssh/known_hosts"
|
||||
```
|
||||
|
||||
修正後先做只寫 `/tmp` 的 smoke,確認 `scp` 與 `ssh` 都通再部署正式檔案:
|
||||
```bash
|
||||
ssh wooo@192.168.0.110 \
|
||||
"printf smoke > /tmp/momo_scp_smoke.txt && \
|
||||
scp /tmp/momo_scp_smoke.txt ollama@192.168.0.188:/tmp/momo_scp_smoke.txt && \
|
||||
ssh ollama@192.168.0.188 'cat /tmp/momo_scp_smoke.txt && rm -f /tmp/momo_scp_smoke.txt' && \
|
||||
rm -f /tmp/momo_scp_smoke.txt"
|
||||
```
|
||||
|
||||
### 3. 重啟容器
|
||||
檔案進入掛載目錄後,重啟容器以加載變更:
|
||||
```bash
|
||||
|
||||
@@ -64,6 +64,12 @@
|
||||
|
||||
## 🆘 故障排除 (Troubleshooting) - 2026-04-28 實戰總結
|
||||
|
||||
### 0. 110 → 188 SCP 報 `Host key verification failed`
|
||||
- **原因**: 110 的 `~/.ssh/known_hosts` 保留了 188 的舊 host key 或缺少目前 key,導致部署檔案傳輸被 SSH 安全檢查擋下。
|
||||
- **修復**: 在 110 執行 `ssh-keygen -R 192.168.0.188 && ssh-keyscan -H 192.168.0.188 >> ~/.ssh/known_hosts`。
|
||||
- **驗證**: 先把 `/tmp/momo_scp_smoke.txt` 從 110 傳到 188 的 `/tmp`,再用 `ssh ollama@192.168.0.188 'cat /tmp/momo_scp_smoke.txt'` 確認可讀,最後刪除 smoke 檔。
|
||||
- **紅線**: 不要把正式部署指令改成長期 `StrictHostKeyChecking=no`;遇到 key 問題要修 known_hosts,而不是關閉驗證。
|
||||
|
||||
### 1. 網站 502 Bad Gateway (Nginx 找不到後端)
|
||||
- **原因**: 110 與 188 之間的 SSH 隧道中斷。
|
||||
- **檢查**: 在 110 執行 `curl -I http://127.0.0.1:5003/health`。
|
||||
@@ -116,3 +122,20 @@
|
||||
- **原因**: Blackbox 或外部探測打 Dashboard 首頁 `/`,會觸發商品看板與 PChome 比價重型查詢;少量 sync worker 被長請求佔滿時,輕量 `/health` 也會排隊逾時。
|
||||
- **檢查**: `docker logs momo-pro-system --since 20m | grep 'Blackbox-Exporter'` 應只看到 `GET /health`;`docker stats momo-db` 若接近多核心滿載,需同步看 `pg_stat_activity` 的 `latest_momo` 類查詢。
|
||||
- **修復**: 188 的 `monitoring/prometheus.yml` 與 110 的 `/home/wooo/monitoring/prometheus.yml` blackbox HTTP targets 必須使用 `/health`;Gunicorn 保持 `worker_class=gthread`、`GUNICORN_THREADS=4`、`preload_app=False`。
|
||||
|
||||
### 12. GCP-A Ollama refused / 110:11435 502
|
||||
- **快速診斷**: 在 repo 根目錄執行 `scripts/ops/diagnose_ollama_gcp_failover.sh`。此腳本不需要 root,也不會修改 nginx、Docker、GCP 或正式服務。
|
||||
- **判讀**:
|
||||
- `GCP-A direct /api/version` 失敗且 `GCP-B direct` 成功:primary VM、防火牆或 Ollama 服務異常;應用層會走 GCP-A → GCP-B → 111,但仍需修 primary。
|
||||
- `110 proxy primary` 502 且 `110 proxy secondary` 成功:110 的 `11435` 固定代理 GCP-A,所以 primary 掛時舊 proxy 入口會失敗;需 110 root 才能改 nginx 或 reload。
|
||||
- `GCP-B embed` 成功但耗時接近 30s:表示 `bge-m3` runner 慢但可用;若經常超過 30s,應處理 GCP-B runner/CPU/模型併發,不要把 111 納入背景 embedding。
|
||||
- **GCP-A 修復方向**:
|
||||
- 有 GCP/SSH 權限時,先確認 VM 是否開機、Firewall 是否開 `22` 與 `11434`、Ollama process 是否在 listen。
|
||||
- 110 現況若 `ssh gcp-a` 回 `port 22: Connection refused`,代表目前跳板無法進主機,不能靠 momo-pro app 修復。
|
||||
- **110 proxy failover 方向**:
|
||||
- 需要 root:`sudo nginx -t`、修改 `/etc/nginx/sites-enabled/110-ollama-proxy.conf`、`sudo systemctl reload nginx`。
|
||||
- 若要讓 `11435` 在 GCP-A 掛時 fallback 到 GCP-B,必須明確標註這是 proxy failover,不代表 GCP-A 已恢復;host health 仍應以 direct GCP-A 探針為準。
|
||||
- **紅線**:
|
||||
- 不要把背景 `bge-m3` 任務改落 111。
|
||||
- 不要用更長 timeout 掩蓋 GCP-A refused;GCP-A 是 primary infra blocker。
|
||||
- 沒有 110 root 或 GCP SSH 權限時,只能完成診斷、文件與應用層降級,不能假裝已修復 primary。
|
||||
|
||||
71
docs/guides/external_professional_benchmark.md
Normal file
71
docs/guides/external_professional_benchmark.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# 外部專業做法 Benchmark
|
||||
|
||||
> 用途:定期把外部電商、商品資料與 UX 專業做法轉成 EwoooC / MOMO Pro 的可執行產品準則。
|
||||
|
||||
## 固定節奏
|
||||
|
||||
- 每週一 09:30 執行外部 benchmark,自動輸出可落地建議。
|
||||
- 只採用能改善核心價值的做法:商品身份比對準確率、可用比價覆蓋率、價格新鮮度、人工覆核效率、競品情報決策品質。
|
||||
- 外部資料必須保留來源、讀取日期、觀察結論與不採用原因。
|
||||
|
||||
## 2026-06-02 初始觀察
|
||||
|
||||
### 1. 商品 identity 必須優先吃結構化 identifiers
|
||||
|
||||
Google Merchant Center 的商品資料規格把 `id`、`brand`、`gtin`、`mpn`、`price`、`availability` 視為商品資料核心;Schema.org / Google Product structured data 也把 `Product`、`Offer`、`AggregateOffer`、`sku`、`gtin`、`brand`、`price`、`availability` 放在商品與報價語意中心。
|
||||
|
||||
落地到本產品:
|
||||
|
||||
- 比對引擎不能只靠商品名稱 token;應逐步建立 `identity_evidence` 欄位,分層保存品牌、SKU、GTIN/條碼、MPN/型號、容量、入數、色號、香味、款式。
|
||||
- 若雙方有 GTIN / MPN / 明確型號,應優先作為 strong evidence。
|
||||
- 若缺 GTIN / MPN,不得自動推定同款;要清楚標示 `identifier_missing` 或 `identifier_weak`。
|
||||
|
||||
### 2. 價格可用性必須和 freshness 綁在一起
|
||||
|
||||
Google Merchant Center 要求價格與庫存狀態要和 landing page / checkout 保持一致;Schema.org Offer 也有 `price`、`priceCurrency`、`availability` 等報價欄位。
|
||||
|
||||
落地到本產品:
|
||||
|
||||
- `decision_ready` 只能計入明確未過期價格,不應把未知 freshness 當可決策。
|
||||
- Dashboard 必須拆開 identity coverage、fresh price coverage、pending identity、stale identity。
|
||||
- 目前 V10.549-V10.565 的方向正確:未知新鮮度不得灌高覆蓋率,並要進刷新/救援流程。
|
||||
|
||||
### 3. 多 offer / 多平台比價應該呈現為 offer evidence,不只是單一低價
|
||||
|
||||
Schema.org `AggregateOffer` 用於同一商品對應多個商家 offer。這個概念適合我們把 MOMO / PChome 的同款證據與價格證據分開保存。
|
||||
|
||||
落地到本產品:
|
||||
|
||||
- `competitor_prices` 應逐步從單一 match row,演進成「identity pair + offer snapshot」兩層。
|
||||
- PPT / AI 決策不只顯示價差,也要顯示 identity confidence、freshness、offer source、last crawled、manual review state。
|
||||
|
||||
### 4. Product comparison UX 要讓使用者比較規格差異
|
||||
|
||||
Baymard 的商品頁與比較 UX 研究強調:使用者需要清楚的 product comparison,尤其是規格驅動品類。
|
||||
|
||||
落地到本產品:
|
||||
|
||||
- 人工覆核頁不能只列 MOMO/PChome 名稱與價格;要突出「不一致欄位」:色號、香味、容量、入數、套組、任選、效期、航空版。
|
||||
- 對 `identity_veto` / `true_low_confidence` 要顯示人可以理解的原因,不只顯示 `待審`。
|
||||
- Dashboard 建議下一步要直接連到對應操作:刷新、補抓、重評、單位價覆核、人工覆核。
|
||||
|
||||
## 目前不採用
|
||||
|
||||
- 不採用「只靠低價/高相似度自動配對」:價格相近不是 identity evidence。
|
||||
- 不採用「大量放寬 threshold 來拉覆蓋率」:會污染核心比價資料。
|
||||
- 不採用「把外部網站 UI 風格直接照搬」:只吸收資訊架構、證據呈現與工作流做法。
|
||||
|
||||
## 下一步 TODO 候選
|
||||
|
||||
1. 建立 `identity_evidence` 正規化 payload,讓 matcher 回傳 identifier/spec/variant evidence。
|
||||
2. 在覆核頁新增差異高亮:色號、香味、容量、入數、任選、效期、來源新鮮度。
|
||||
3. 將 PPT / AI payload 的比價項目拆成 identity evidence 與 offer evidence。
|
||||
4. 每週 benchmark 結果若命中上述 TODO,回寫 `TODO_NEXT_STEPS.txt` 或新增 ADR / memory。
|
||||
|
||||
## 參考來源
|
||||
|
||||
- Google Merchant Center Product data specification: https://support.google.com/merchants/answer/7052112
|
||||
- Google Search Central Product structured data: https://developers.google.com/search/docs/appearance/structured-data/product
|
||||
- Schema.org Product / Offer / AggregateOffer: https://schema.org/Product, https://schema.org/Offer, https://schema.org/AggregateOffer
|
||||
- Baymard Product Page UX Best Practices: https://baymard.com/blog/current-state-ecommerce-product-page-ux
|
||||
- Baymard Product Comparison UX: https://baymard.com/blog/provide-comparison-features
|
||||
@@ -1,11 +1,12 @@
|
||||
# Google Drive API 設定指南
|
||||
|
||||
## 📋 功能說明
|
||||
系統自動化流程:
|
||||
1. **每 30 分鐘**檢查 Google Drive `當日業績匯入` 資料夾。
|
||||
2. **自動下載** `即時業績_當日.xlsx`。
|
||||
3. **自動匯入** 至資料庫 `daily_sales_snapshot`。
|
||||
4. **歸檔** 原始檔案。
|
||||
系統自動化流程(PChome 後台業績匯出):
|
||||
1. PChome 後台業績 Excel 先被放入 Google Drive `當日業績匯入` 資料夾。
|
||||
2. `momo-scheduler` **每 30 分鐘**檢查待匯入檔案。
|
||||
3. 自動下載 `即時業績_當日.xlsx` 或符合設定 pattern 的 Excel。
|
||||
4. 自動辨識明細 worksheet / 表頭列,匯入至 `daily_sales_snapshot` 並同步 `realtime_sales_monthly`。
|
||||
5. 成功檔案歸檔至 `已匯入`;格式或日期不合格的檔案移至 `匯入失敗`,避免重複告警。
|
||||
|
||||
---
|
||||
|
||||
@@ -30,3 +31,6 @@ python3 -c "from services.google_drive_service import drive_service; drive_servi
|
||||
## 📁 資料夾結構要求
|
||||
Google Drive 根目錄必須存在:
|
||||
`我的雲端硬碟/業績報表/當日業績/`
|
||||
|
||||
> 後續若要把 PChome 後台人工匯出改成全自動,先閱讀
|
||||
> `docs/guides/pchome_sales_import_automation.md`。
|
||||
|
||||
75
docs/guides/pchome_sales_import_automation.md
Normal file
75
docs/guides/pchome_sales_import_automation.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# PChome 後台業績匯出自動化
|
||||
|
||||
## 目的
|
||||
|
||||
把目前人工從 PChome 後台匯出業績 Excel 的流程,自動接到既有匯入管線:
|
||||
|
||||
```text
|
||||
PChome 後台業績匯出
|
||||
-> Google Drive 當日業績匯入/
|
||||
-> momo-scheduler 每 30 分鐘 auto_import
|
||||
-> daily_sales_snapshot + realtime_sales_monthly
|
||||
-> daily / growth / PPT / AI 分析
|
||||
```
|
||||
|
||||
## 目前已完成
|
||||
|
||||
- V10.605 起,`services/import_service.py` 已可自動辨識 PChome 後台匯出的多工作表 Excel。
|
||||
- 匯入器會掃描所有 worksheet 的前 15 列表頭,優先選擇含日期、商品名稱、總業績或銷售金額的明細工作表。
|
||||
- 匯入成功後檔案會移到 `已匯入`。
|
||||
- 格式或日期不合格的檔案會移到 `匯入失敗`,避免每 30 分鐘重複告警。
|
||||
- `daily_sales_snapshot."商品ID"` 是 PChome 後台業績匯出的商品識別碼,不可預設等於 MOMO `products.i_code`。
|
||||
|
||||
## 推薦方案
|
||||
|
||||
### 方案 A:PChome 後台排程寄信或固定附件
|
||||
|
||||
這是首選。若 PChome 後台能排程寄出報表,或每日寄到 Gmail:
|
||||
|
||||
1. PChome 後台每天固定寄出業績 Excel。
|
||||
2. Gmail / n8n 監聽寄件者、主旨、附件檔名。
|
||||
3. 驗證附件日期與檔名,例如 `即時業績_當日.xlsx` 或 `pchome_sales_YYYYMMDD.xlsx`。
|
||||
4. 將附件存到 Google Drive `當日業績匯入/`。
|
||||
5. 既有 `run_auto_import_task` 自動匯入。
|
||||
|
||||
優點:穩定、低維護、無需模擬登入、較不受後台 UI 改版影響。
|
||||
|
||||
### 方案 B:受控瀏覽器自動下載
|
||||
|
||||
若 PChome 後台只能人工登入後匯出:
|
||||
|
||||
1. 在 110 或獨立 browser worker 跑 Playwright / n8n browser step。
|
||||
2. 使用密鑰管理保存帳密,不寫入 repo。
|
||||
3. 登入 PChome 後台,選日期範圍,下載業績 Excel。
|
||||
4. 檢查下載檔大小、sheet、日期最大值。
|
||||
5. 上傳到 Google Drive `當日業績匯入/`。
|
||||
6. 交由既有 importer 匯入。
|
||||
|
||||
注意:若有 OTP、驗證碼、裝置信任或密碼到期,此方案需要 HITL fallback,不應讓瀏覽器在使用者 Mac 上反覆跳出授權視窗。
|
||||
|
||||
### 方案 C:Telegram 上傳備援
|
||||
|
||||
若 PChome 後台登入自動化暫時不穩:
|
||||
|
||||
1. 使用者將 Excel 直接丟給 Telegram bot。
|
||||
2. bot 驗證檔案名稱、日期、sheet、欄位。
|
||||
3. 通過則存到 Google Drive 或直接呼叫 importer。
|
||||
4. 不通過則回覆可讀錯誤,例如缺欄位、資料過舊、找不到明細 sheet。
|
||||
|
||||
此方案不是全自動,但可以把「下載後匯入」從人工操作降到傳檔即可。
|
||||
|
||||
## 實作順序
|
||||
|
||||
1. 先確認 PChome 後台是否支援排程寄信、Email 附件、FTP、API 或固定下載 URL。
|
||||
2. 若有 Email / 附件,優先用 n8n / Gmail API 走方案 A。
|
||||
3. 若沒有,再做方案 B 的 browser worker;worker 不跑在使用者桌面,避免密碼提示與彈窗干擾。
|
||||
4. 保留方案 C 作為緊急備援。
|
||||
5. 每次自動取得檔案後,都先跑 importer 的 sheet / 日期 / 欄位 preflight,再進正式匯入。
|
||||
|
||||
## 驗收條件
|
||||
|
||||
- 不需人工打開後台即可產生或取得當日 PChome 業績 Excel。
|
||||
- Google Drive `當日業績匯入/` 有新檔後,30 分鐘內自動匯入。
|
||||
- `daily_sales_snapshot` 與 `realtime_sales_monthly` 最新日期落後不超過 1 天。
|
||||
- `/daily_sales` 與 `/growth_analysis` 圖表 smoke 通過。
|
||||
- 失敗檔只告警一次,並被移到 `匯入失敗`。
|
||||
72
docs/guides/webcrumbs_shared_runtime.md
Normal file
72
docs/guides/webcrumbs_shared_runtime.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Webcrumbs 共用 UI Runtime 接入手冊
|
||||
|
||||
> 目的:讓 momo-pro 與其他專案共用同一套自架 Webcrumbs microfrontend runtime,避免每個專案各自引用官方 CDN 或複製 runtime。
|
||||
|
||||
## 定位
|
||||
|
||||
- Webcrumbs 在本專案只作為 microfrontend/plugin loader。
|
||||
- 不把 Webcrumbs 上游 repo 整包放進 momo-pro 主服務。
|
||||
- 不依賴官方 `app.webcrumbs.ai` 或官方 `cdn.webcrumbs.dev/@latest`。
|
||||
- 生產環境必須走自架 runtime,並固定版本。
|
||||
|
||||
## momo-pro 設定
|
||||
|
||||
`.env` 建議值:
|
||||
|
||||
```env
|
||||
WEBCRUMBS_ENABLED=true
|
||||
WEBCRUMBS_BASE_URL=https://webcrumbs.wooo.work
|
||||
WEBCRUMBS_RUNTIME_VERSION=shared-ui-poc-0.1.0
|
||||
WEBCRUMBS_RUNTIME_PATH=/webcrumbs-assets/loader/webcrumbs-compatible-loader.js
|
||||
WEBCRUMBS_RUNTIME_URL=
|
||||
WEBCRUMBS_PLUGIN_BASE_URL=/webcrumbs-assets/plugins
|
||||
WEBCRUMBS_ASSET_UPSTREAM_URL=http://192.168.0.188:18088
|
||||
```
|
||||
|
||||
目前正式頁面預設走 momo-pro 同源 asset proxy:
|
||||
|
||||
- `/webcrumbs-assets/loader/...`
|
||||
- `/webcrumbs-assets/plugins/...`
|
||||
- `/webcrumbs-assets/demo/...`
|
||||
|
||||
這樣即使 `webcrumbs.wooo.work` 的公網 TLS 或 Basic Auth 尚未完全收斂,momo-pro 頁面仍可用 `mo.wooo.work` 同源載入 loader 與 plugin。若日後要改回獨立 Webcrumbs CDN,直接設定:
|
||||
|
||||
```env
|
||||
WEBCRUMBS_RUNTIME_URL=https://webcrumbs.wooo.work/shared-ui/loader/webcrumbs-compatible-loader.js
|
||||
WEBCRUMBS_PLUGIN_BASE_URL=https://webcrumbs.wooo.work/shared-ui/plugins
|
||||
```
|
||||
|
||||
## 頁面嵌入方式
|
||||
|
||||
全站 runtime 由 `templates/ewoooc_base.html` 載入。頁面只需要放 plugin tag:
|
||||
|
||||
```html
|
||||
<stock-platform-plugin uri="/webcrumbs-assets/plugins/finance.market-ticker-strip/0.1.0"></stock-platform-plugin>
|
||||
```
|
||||
|
||||
Plugin 目錄至少應提供:
|
||||
|
||||
```text
|
||||
bundle.js
|
||||
style.css
|
||||
```
|
||||
|
||||
## 治理規則
|
||||
|
||||
- Plugin URI 必須使用自架 domain。
|
||||
- 在 momo-pro 正式頁面,優先使用同源 `/webcrumbs-assets/` URI。
|
||||
- Plugin 目錄必須版本化,不使用浮動 `latest`。
|
||||
- Plugin 不得包含 secret、token、內部 API key。
|
||||
- Plugin 若需要正式資料,必須讀 momo-pro 既有 API,不得自行打 DB。
|
||||
- 修改 Webcrumbs runtime 本體時需注意 AGPL-3.0 授權義務。
|
||||
|
||||
## 驗收
|
||||
|
||||
- `/webcrumbs` 顯示 runtime URL、版本與 plugin base。
|
||||
- `/webcrumbs` 會嵌入同源 `/webcrumbs-assets/plugins/...` 的 live plugin preview,並由 momo-pro 注入只讀 MOMO/PChome exact 價差摘要;若資料源不可用或無風險候選,改注入診斷空狀態,避免 plugin fallback demo 數字在正式頁面被誤認成真實市場或 AI 建議。
|
||||
- `/api/webcrumbs/marketplace-host-data` 會回傳同一份登入後只讀 host data contract,供 plugin / QA / 其他專案 proxy 驗證;即使全站 `DISABLE_LOGIN=true`,此 API 仍必須要求登入 session 或 `X-Internal-Key`,boundary 必須標示 `writes_database=false`、`calls_llm=false`、`fetches_external=false`。
|
||||
- Host data metadata 需同時輸出 `matched_count/coverage_rate` 與 `fresh_match_count/fresh_match_rate/stale_match_count/pending_match_count`,讓共用 UI 分清「身份覆蓋」與「價格新鮮度」。
|
||||
- `/webcrumbs` 未取得登入 session 或 `X-Internal-Key` 時只能注入 `auth_required` 空狀態,不得把真實 SKU、價格或價差寫進 inline seed data。
|
||||
- `/webcrumbs-assets/loader/webcrumbs-compatible-loader.js` 回 200 且 content type 是 JavaScript。
|
||||
- `ewoooc_base.html` 在 `WEBCRUMBS_ENABLED=true` 且 runtime URL 有效時輸出 `<script data-webcrumbs-runtime=...>`。
|
||||
- 任一試點頁嵌入 plugin 後,瀏覽器 console 不應有 `MISSING_URI`、`STYLE_LOAD_ERROR`、`SCRIPT_LOAD_ERROR`。
|
||||
@@ -13,6 +13,7 @@
|
||||
| 檔案 | 用途 | 何時閱讀 |
|
||||
|---|---|---|
|
||||
| `history_logs.md` | 重大里程碑與歷史脈絡 | 要理解演進背景、排查「為何變成這樣」時 |
|
||||
| `current_execution_queue_20260524.md` | 目前核心產品推進佇列,涵蓋比價、圖表、PPT、觀測台、外部入口、效能與部署驗證 | 接續本輪「批准繼續」、避免漏掉工作項目、需要新 session 交接時 |
|
||||
| `ai_automation_closure_20260429.md` | 四 AI Agent 自動化閉環、Smoke、metrics 與 Grafana 觀測實況 | 接續 AI 自動化、EventRouter、AutoHeal、OpenClaw memory、ElephantAlpha 編排、可觀測性時 |
|
||||
| `credentials_passbook.md` | 伺服器、帳密、埠位對照 | 需要維運、部署、憑證核對時 |
|
||||
| `feedback_db_metadata_import.md` | SQLAlchemy metadata / `create_all()` 漏表鐵律 | 新增 model、修 schema、排查 fresh env 漏表時 |
|
||||
@@ -33,6 +34,9 @@
|
||||
- `docs/guides/observability_ui_governance.md`
|
||||
用途:AI 觀測台 `/observability/*` 頁面的 UI/UX 設計治理、禁用事項與巡檢指令。
|
||||
何時閱讀:新增或修改觀測台頁面、側欄、Chart.js、資料密集卡片/表格、空狀態時。
|
||||
- `docs/guides/pchome_sales_import_automation.md`
|
||||
用途:PChome 後台業績匯出自動化,銜接 Google Drive auto import、失敗隔離與下游 daily/growth/PPT/AI。
|
||||
何時閱讀:要把 PChome 後台業績匯出從人工下載改成 Email / n8n / browser worker / Telegram 備援自動化時。
|
||||
|
||||
## 新增規則
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
- PPT vision、PPT 文案 final fallback 與 MCP 離線 final fallback 已改走 `OllamaService.generate()`;`OllamaService.generate()` 支援 `options`、`keep_alive` 與 vision `images`,特殊 `/api/generate` 路徑同樣取得三主機 retry。
|
||||
- OpenClaw QA / daily Hermes template / NemoTron qwen3 的 flag 文件與測試已對齊 Ollama-first 預設 ON;顯式 `false` 才是 Gemini/NIM legacy 緊急退路。OpenClaw QA 已移除單一 `OPENCLAW_QA_OLLAMA_HOST` 主機覆寫,`_call_qwen3_qa()` 改走 `OllamaService` 的 GCP-A → GCP-B → 111 retry 並回寫實際 provider。
|
||||
- Code Review pipeline 已對齊 Ollama-first:`_hermes_scan()` 與 `_openclaw_assess()` 都先走 `OllamaService` 的 GCP-A → GCP-B → 111 retry;Gemini 僅在 Ollama(與可選 Claude)失敗後以 `code_review_openclaw_gemini` caller 記錄備援,不再以 `code_review_openclaw` 直接 Gemini-first。
|
||||
- Telegram 圖片商品辨識已對齊 Ollama-first:`routes/openclaw_bot_routes.py` 會先用 `OPENCLAW_IMAGE_VISION_MODEL` 透過 `OllamaService` retry GCP-A → GCP-B → 111;Gemini 只以 `openclaw_bot_image_gemini` caller 作為圖片辨識備援。
|
||||
- `.env.example` 已補齊 Python runtime 實際讀取的環境變數,`tests/test_phase3f_cleanup_contracts.py::test_env_example_documents_runtime_os_env_keys` 會掃 `app.py/config.py/scheduler.py/run_scheduler.py/routes/services/utils` 的 `os.getenv()` / `os.environ.get()`;只允許 `PYTEST_CURRENT_TEST` 與 `MOMO_ALLOW_INSECURE_CONFIG_FOR_TESTS` 兩個測試內部 key 不進範例。
|
||||
- `docker-compose*.yml` 使用的 `${VAR}` 也已納入 `.env.example` 契約,包含 MCP compose 的 `TAVILY_API_KEY`、`EXA_API_KEY`、`MCP_POSTGRES_PASSWORD`、`FIRECRAWL_AUTH_KEY`,以及 image tag / Grafana / pgAdmin / Metabase / Grist 變數;`test_env_example_documents_docker_compose_variables` 會守住。
|
||||
- Market Intel `seed_writer_cli_status` route 已補 API 層回歸:即使 `execute=true` 且環境有 `MARKET_INTEL_SEED_WRITE_APPROVAL`,API 仍不得回吐 token / `approval_token_hint` / 固定 token 文案,且不得 ready 或寫入;`tests/test_market_intel_skeleton.py::test_seed_writer_cli_status_route_never_leaks_approval_token` 會守住。
|
||||
|
||||
@@ -4,12 +4,73 @@
|
||||
|
||||
## 盤點結論
|
||||
|
||||
- Python 總量:約 90,293 行(排除 `venv/`、`backups/`、`__pycache__/`、`.claude/worktrees/`)。
|
||||
- 最大壓力區:`services/` 約 42,364 行、`routes/` 約 29,511 行。
|
||||
- Python 總量:約 139,476 行(排除 `venv/`、`backups/`、`__pycache__/`、`.claude/worktrees/`)。
|
||||
- 最大壓力區:`services/` 約 84,159 行、`routes/` 約 36,245 行。
|
||||
- `app.py` 目前約 1,232 行,功能定位應固定為 bootstrap / Blueprint registration / startup guard,不再承接新 route。
|
||||
- 目前工作樹仍有 24 個 Python 檔案達到或超過 800 行;這些不是禁止修 bug,而是禁止繼續塞新功能。
|
||||
- 目前工作樹仍有 33 個 Python 檔案達到或超過 800 行;這些不是禁止修 bug,而是禁止繼續塞新功能。
|
||||
- 2026-05-05 追記:Phase 38→56 觀測台戰役讓 `routes/admin_observability_routes.py` 與 `run_scheduler.py` 進入大檔治理清單;後續觀測台功能應先抽 query/action service,不再把新 SQL 與 L2 mutation 直接塞回 route。
|
||||
- 2026-05-06 追記:跨平台市場情報模組啟動前,必須先把新增爬蟲、排程、DB schema、UI route 全部隔離在 `market_*` / `services/market_intel/` / `routes/market_intel_routes.py`,不可塞回既有大檔。
|
||||
- 2026-05-18 追記:Phase 42 市場情報只在 `app.py` 的 `EXPECTED_METADATA_TABLES` 補上 `market_alert_review_queue` 名稱,未新增 route / bootstrap 邏輯;後續仍應把 metadata verification 抽到 app factory 或 startup guard module,避免 `app.py` 繼續承接功能。
|
||||
- 2026-05-19 追記:同步治理測試盤點,校正 `routes/admin_observability_routes.py` 行數;此處只更新 inventory,不變更觀測台功能。
|
||||
- 2026-05-19 追記:V10.229 之後 `services/ppt_vision_service.py` 進入 800 行治理清單;本次只補 inventory 讓守門測試反映現況,不變更 PPT 視覺 QA 功能。
|
||||
- 2026-05-24 追記:同步 V10.460 ElephantAlpha decision_envelope helper 後的 `services/elephant_alpha_autonomous_engine.py` 行數;此處只更新 inventory,不變更 AI engine 行為。
|
||||
- 2026-05-19 追記:同步背景 V10.276 ElephantAlpha 更新後的 `services/elephant_alpha_autonomous_engine.py` 行數;此處只更新 inventory,不變更 AI engine 行為。
|
||||
- 2026-05-19 追記:同步背景 V10.281/V10.282 dashboard 與 Code Review pipeline 更新後的行數;此處只更新 inventory,不變更 dashboard 或 code review 行為。
|
||||
- 2026-05-19 追記:同步背景 PChome identity 價格刷新與競品風險查詢更新後的 `services/competitor_price_feeder.py` 行數;此處只更新 inventory,不變更 feeder 行為。
|
||||
- 2026-05-19 追記:同步背景 Telegram 模板擴充後的 `services/telegram_templates.py` 行數;此處只更新 inventory,不變更 Telegram 格式化行為。
|
||||
- 2026-05-20 追記:同步背景 PChome unit-comparable identity 更新後的 `services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更商品比對行為。
|
||||
- 2026-05-20 追記:同步背景商品看板覆核快取回填後的 `services/competitor_intel_repository.py` 行數;此處只更新 inventory,不變更競品情報 repository 行為。
|
||||
- 2026-05-20 追記:同步背景商品看板比價覆核狀態分流後的 `routes/dashboard_routes.py` 行數;此處只更新 inventory,不變更 dashboard 行為。
|
||||
- 2026-05-20 追記:同步背景匯出流程更新後的 `routes/export_routes.py` 行數;此處只更新 inventory,不變更 export 行為。
|
||||
- 2026-05-20 追記:同步背景 PChome feeder 人工覆核回饋採納後的 `services/competitor_price_feeder.py` 行數;此處只更新 inventory,不變更 feeder 行為。
|
||||
- 2026-05-20 追記:同步背景 PChome 比價人工覆核閉環後的 `services/competitor_intel_repository.py` 行數;此處只更新 inventory,不變更競品情報 repository 行為。
|
||||
- 2026-05-20 追記:同步背景 PChome identity / price direction 更新後的 `services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更商品比對行為。
|
||||
- 2026-05-20 追記:同步背景 PChome crawler 搜尋韌性擴充後的 `services/pchome_crawler.py` 行數;此處只更新 inventory,不變更 PChome crawler 行為。
|
||||
- 2026-05-20 追記:同步 PChome 近門檻候選重評與 matcher 系列/刀片數防錯配更新後的 `services/marketplace_product_matcher.py`、`services/competitor_price_feeder.py` 行數;此處只更新 inventory,不變更比價行為。
|
||||
- 2026-05-20 追記:同步 PChome 搜尋詞品質層、候選召回與 hard-veto 狀態分流更新後的 `services/marketplace_product_matcher.py`、`services/competitor_price_feeder.py` 行數;並補列背景市場情報 deployment readiness 大檔,僅更新 inventory。
|
||||
- 2026-05-20 追記:同步 PChome 搜尋詞特定品線優先級更新後的 `services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-20 追記:同步 PChome 共享 identity anchor scorer 與市場情報 review report route 進入大檔門檻後的行數;此處只更新 inventory,不變更功能。
|
||||
- 2026-05-20 追記:同步 PChome contained identity anchor scorer 更新後的 `services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-20 追記:同步 PChome spec/name alignment near-threshold scorer 更新後的 `services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-20 追記:同步市場情報 review report route 與 review receipt 巨檔現況,並校正 PChome fresh-search recovery 更新後的 `services/competitor_price_feeder.py`、`services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-21 追記:同步 Browse.sh 診斷導入、PChome 變體搜尋與色號防錯配更新後的 `services/marketplace_product_matcher.py` 行數,並校正市場情報 review report route 目前行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-21 追記:同步市場情報 MCP runtime smoke receipt gate 後的 `routes/market_intel_routes.py` 與 `services/market_intel/deployment_readiness.py` 行數;本次 route 只承接既有 Blueprint glue,後續新增 MCP/UI gate 應優先拆出子 Blueprint 或 route registration helper。
|
||||
- 2026-05-21 追記:同步 111 fallback context/resource guard 合併後的 `services/ollama_service.py` 行數;此處只更新 inventory,不變更 Ollama 路由行為。
|
||||
- 2026-05-21 追記:同步專業比價分級連動合併後的 `services/competitor_intel_repository.py` 與 `services/nemoton_dispatcher_service.py` 行數;此處只更新 inventory,不變更比價或告警行為。
|
||||
- 2026-05-21 追記:同步市場情報 MCP runtime promotion gate 後的 `routes/market_intel_routes.py` 與 `services/market_intel/deployment_readiness.py` 行數;此處只更新 inventory,後續市場情報 MCP route 應拆出子 Blueprint。
|
||||
- 2026-05-24 追記:同步市場情報 MCP manual fetch handoff gate 後的 `routes/market_intel_routes.py` 與 `services/market_intel/deployment_readiness.py` 行數;本次新增邏輯已放在獨立 `services/market_intel/mcp_manual_fetch_handoff.py`,route 僅保留薄 glue,下一個 MCP/UI gate 應優先拆出子 Blueprint 或 route registration helper。
|
||||
- 2026-05-24 追記:同步市場情報 MCP fetch target review gate 後的 `routes/market_intel_routes.py` 與 `services/market_intel/deployment_readiness.py` 行數;本次新增邏輯已放在獨立 `services/market_intel/mcp_fetch_target_review.py`,route 僅保留薄 glue,後續市場情報 MCP route 應拆出子 Blueprint 或 route registration helper。
|
||||
- 2026-05-24 追記:同步市場情報 MCP fetch run package gate 後的 `routes/market_intel_routes.py` 與 `services/market_intel/deployment_readiness.py` 行數;本次新增 endpoint 已拆到 `routes/market_intel_mcp_run_routes.py`,主 Blueprint 只新增 extension import,後續 MCP route 應延續此模式。
|
||||
- 2026-05-24 追記:同步市場情報 MCP fetch run readiness gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增 endpoint 延續 `routes/market_intel_mcp_run_routes.py` route extension,新增邏輯放在獨立 `services/market_intel/mcp_fetch_run_readiness.py`。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue review gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增邏輯維持在獨立 `services/market_intel/mcp_fetch_candidate_queue_review.py`,route 延續 `routes/market_intel_mcp_run_routes.py` extension。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue writer preflight gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增邏輯維持在獨立 `services/market_intel/mcp_fetch_candidate_queue_writer_preflight.py`,route 延續 `routes/market_intel_mcp_run_routes.py` extension。
|
||||
- 2026-05-31 追記:`services/market_intel/mcp_fetch_candidate_queue_writer_preflight.py` 目前 628 行,略過 600 行提醒門檻;暫不拆分的理由是 gate 條件、sample payload 與 side-effect blocklist 需留在單一 preview module 便於審核,下一個 writer CLI review gate 若共用相同常數再抽 `mcp_fetch_candidate_queue_writer_policy.py`。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue writer CLI review gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增 `services/market_intel/mcp_fetch_candidate_queue_writer_cli_review.py` 為 591 行,仍低於 600 行提醒門檻。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue writer run package review gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增 `services/market_intel/mcp_fetch_candidate_queue_writer_run_package_review.py` 為 660 行,略過 600 行提醒門檻。暫不拆分的理由是 run package gate 需要把 CLI review linkage、artifact manifest、operator shell command sequence 與 side-effect blocklist 放在單一 preview module 便於安全審核;若下一段 run readiness review 重複相同 policy,應抽出 writer policy helper。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue writer run readiness gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增 `services/market_intel/mcp_fetch_candidate_queue_writer_run_readiness.py` 為 640 行,略過 600 行提醒門檻。暫不拆分的理由是 readiness gate 需同時審核上一段 run package review linkage、operator artifact path policy、CLI-only/token-only confirmation 與 side-effect blocklist;若後續 run receipt gate 再重複 policy,應抽出 `mcp_fetch_candidate_queue_writer_policy.py`。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue writer run receipt review gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增 `services/market_intel/mcp_fetch_candidate_queue_writer_run_receipt_review.py` 為 688 行,略過 600 行提醒門檻。暫不拆分的理由是 receipt gate 需同時審核 readiness linkage、receipt identity、artifact path policy、operator confirmation、token redaction 與 side-effect blocklist;若下一段 closeout gate 重複相同 path/side-effect policy,應抽出 `mcp_fetch_candidate_queue_writer_policy.py`。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue writer run closeout review gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增 `services/market_intel/mcp_fetch_candidate_queue_writer_run_closeout_review.py` 為 724 行,略過 600 行提醒門檻。暫不拆分的理由是 closeout gate 需同時審核 receipt review linkage、artifact manifest preservation、rollback note、read-only inventory next-step、lightweight preview sample 與 side-effect blocklist;若下一段 post-closeout inventory gate 重複 path/side-effect policy,應抽出 `mcp_fetch_candidate_queue_writer_policy.py`。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue writer post-closeout inventory review gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增 `services/market_intel/mcp_fetch_candidate_queue_writer_post_closeout_inventory_review.py` 為 649 行,略過 600 行提醒門檻。暫不拆分的理由是 inventory gate 需同時審核 closeout linkage、read-only inventory 摘要、artifact path policy、operator boundary confirmation 與 side-effect blocklist;後續若 candidate queue review handoff 再複用同一套 path/side-effect policy,應抽出 `mcp_fetch_candidate_queue_writer_policy.py`。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue writer review handoff gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次將 sample payload 抽到 `services/market_intel/mcp_fetch_candidate_queue_writer_review_handoff_sample.py`(105 行),主 gate `services/market_intel/mcp_fetch_candidate_queue_writer_review_handoff.py` 降為 571 行,低於 600 行提醒門檻;後續若 candidate queue review inventory 繼續複用 path/side-effect policy,應抽出 `mcp_fetch_candidate_queue_writer_policy.py`。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue writer review inventory gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增 `services/market_intel/mcp_fetch_candidate_queue_writer_review_inventory.py`(462 行)、`services/market_intel/mcp_fetch_candidate_queue_writer_review_inventory_gates.py`(183 行)與 `services/market_intel/mcp_fetch_candidate_queue_writer_review_inventory_sample.py`(107 行),全部低於 600 行提醒門檻;`routes/market_intel_mcp_run_routes.py` 目前 717 行,仍低於 800 行但後續新增 MCP gate 應持續評估拆第二個 route extension。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue writer review decision gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增 `services/market_intel/mcp_fetch_candidate_queue_writer_review_decision.py`(498 行)、`services/market_intel/mcp_fetch_candidate_queue_writer_review_decision_gates.py`(241 行)與 `services/market_intel/mcp_fetch_candidate_queue_writer_review_decision_sample.py`(118 行),全部低於 600 行提醒門檻;`routes/market_intel_mcp_run_routes.py` 目前 772 行,仍低於 800 行但已接近門檻,下一段 MCP route 應優先拆第二個 route extension。
|
||||
- 2026-05-31 追記:同步市場情報 MCP fetch candidate queue writer review decision approval gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增 `services/market_intel/mcp_fetch_candidate_queue_writer_review_decision_approval.py`(560 行)、`services/market_intel/mcp_fetch_candidate_queue_writer_review_decision_approval_gates.py`(255 行)、`services/market_intel/mcp_fetch_candidate_queue_writer_review_decision_approval_sample.py`(140 行)與 `routes/market_intel_mcp_review_routes.py`(64 行),全部低於 600 行提醒門檻;`routes/market_intel_mcp_run_routes.py` 維持 770 行,本次未再加 endpoint,改以第二個 MCP review route extension 承接。
|
||||
- 2026-06-01 追記:同步市場情報 Professional Source Governance gate 後的 `services/market_intel/deployment_readiness.py` 行數;本次新增 `services/market_intel/mcp_professional_source_governance.py`(391 行)、`services/market_intel/mcp_professional_source_governance_gates.py`(266 行)、`services/market_intel/mcp_professional_source_governance_sample.py`(175 行)與 `routes/market_intel_mcp_review_routes.py`(165 行),全部低於 600 行提醒門檻;`services/market_intel/deployment_readiness.py` 仍是既有 P2 大檔,只加 preview-safe check 與 smoke target,後續需延續小 service + route extension 模式。
|
||||
- 2026-06-03 追記:新增 `services/market_intel/mcp_fetch_target_source_governance_review.py`(237 行),並將 `mcp_professional_source_governance_sample.py` 擴為 307 行、`routes/market_intel_mcp_review_routes.py` 擴為 207 行;新增服務仍低於 600 行提醒門檻。`services/market_intel/deployment_readiness.py` 擴為 2010 行,仍屬既有 P2 大檔,後續應優先拆 readiness smoke/check registration。
|
||||
- 2026-05-24 追記:同步背景 Code Review 111 fallback 保護合併後的 `services/code_review_pipeline_service.py` 行數;此處只更新 inventory,不變更 Code Review 行為。
|
||||
- 2026-05-21 追記:同步 PChome/LUDEYA 商品線名稱漂移比對更新後的 `services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-21 追記:同步 MAC/Yuskin/AHC 名稱漂移與 bundle equivalent matcher 更新後的 `services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-21 追記:同步 EDM 失效頁 alert guard 與 REJURAN 唇膏寬價差 exact-identity matcher 更新後的 `scheduler.py`、`services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-21 追記:同步過期 EDM / seasonal promo crawler 排程改為 opt-in、NIVEA/OPI 搜尋 noise 與 identity anchor 補強後的 `run_scheduler.py`、`services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-21 追記:同步 Recipe Box 多效提亮防曬霜同款漂移比對補強後的 `services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-21 追記:同步 browse.sh 診斷計畫寫入 `competitor_match_attempts` 後的 `services/competitor_price_feeder.py` 行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-24 追記:同步背景 PChome 近門檻身份回收與 focused identity 系列更新後的 `services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更商品比對行為。
|
||||
- 2026-05-24 追記:同步 111 fallback circuit breaker、NemoTron 決策信封與 Telegram template governance 後的 `run_scheduler.py`、`services/ollama_service.py`、`services/nemoton_dispatcher_service.py`、`services/telegram_templates.py` 行數;此處只更新 inventory,不變更模組化決策。
|
||||
- 2026-05-24 追記:同步 PChome 覆核頁 fast-count、輕量 render 與重算可採用指標後的 `routes/dashboard_routes.py` 行數;此處只更新 inventory,不變更 dashboard 行為。
|
||||
- 2026-05-24 追記:同步 PChome rescore audit 最新狀態口徑與單位價 multiplier 修正後的 `services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更拆分策略。
|
||||
- 2026-05-24 追記:同步 PChome review queue 決策信封合併後的 `services/competitor_intel_repository.py` 行數;此處只更新 inventory,不變更拆分策略。
|
||||
- 2026-05-25 追記:同步背景 embedding 讀取 `host_health_probes` skip guard 後的 `services/ollama_service.py` 行數;此處只更新 inventory,不變更 Ollama 路由決策。
|
||||
- 2026-05-29 追記:同步 PChome near-threshold / focused identity 回收系列後的 `services/marketplace_product_matcher.py` 行數;此處只更新 inventory,不變更拆分策略。
|
||||
|
||||
## 達到或超過 800 行檔案清單
|
||||
|
||||
@@ -18,27 +79,38 @@
|
||||
| 9225 | `routes/openclaw_bot_routes.py` | P0 巨型 Blueprint | route / bot command service / report service / scheduler hook;禁止再新增市場情報入口 |
|
||||
| 5499 | `services/ppt_generator.py` | P0 報表生成巨型 service | deck orchestration / slide builders / chart builders / report type registry |
|
||||
| 3186 | `routes/sales_routes.py` | P0 巨型 Blueprint | page routes / API routes / chart query service / calendar service;分析頁新增功能先抽 `services/sales/` |
|
||||
| 2821 | `scheduler.py` | P0 排程總管 | task registry / crawler jobs / report jobs / notification jobs;市場情報只能透過獨立 job module 掛入 |
|
||||
| 2973 | `scheduler.py` | P0 排程總管 | task registry / crawler jobs / report jobs / notification jobs;市場情報只能透過獨立 job module 掛入 |
|
||||
| 2731 | `services/openclaw_strategist_service.py` | P0 OpenClaw service | prompt builders / report composer / strategy rules |
|
||||
| 2657 | `routes/admin_observability_routes.py` | P0 觀測台巨型 Blueprint | `services/observability_query_service.py` / `services/observability_action_service.py` / route glue |
|
||||
| 3681 | `routes/admin_observability_routes.py` | P0 觀測台巨型 Blueprint | `services/observability_query_service.py` / `services/observability_action_service.py` / route glue |
|
||||
| 1796 | `routes/ai_routes.py` | P1 AI Blueprint | route glue / AI orchestration service / prompt builders |
|
||||
| 1721 | `services/nemoton_dispatcher_service.py` | P1 NemoTron service | NIM client / tool-call parser / action dispatcher |
|
||||
| 1507 | `routes/dashboard_routes.py` | P1 Dashboard Blueprint | competitor decision overview / dashboard query service;首頁資料整併需抽 service |
|
||||
| 2154 | `services/nemoton_dispatcher_service.py` | P1 NemoTron service | NIM client / tool-call parser / action dispatcher |
|
||||
| 2535 | `routes/dashboard_routes.py` | P1 Dashboard Blueprint | competitor decision overview / dashboard query service;首頁資料整併需抽 service |
|
||||
| 1485 | `routes/vendor_routes.py` | P1 Vendor Blueprint | route glue / stockout mutation/email;V2 page query、stockout list/batches API query、vendor list/detail query 已抽到 `services/vendor_stockout_query_service.py` |
|
||||
| 1390 | `services/telegram_bot_service.py` | P1 Telegram service | command handlers / message formatters / bot client |
|
||||
| 1232 | `app.py` | P1 bootstrap | 保持只做 app setup;繼續往 app_factory / extension setup 抽 |
|
||||
| 1144 | `services/elephant_alpha_autonomous_engine.py` | P1 ElephantAlpha engine | HITL / executor / planning policy |
|
||||
| 1090 | `routes/cicd_routes.py` | P2 CI/CD Blueprint | route glue / CI query service / deployment action service |
|
||||
| 1017 | `run_scheduler.py` | P2 scheduler entrypoint | observability jobs / token report jobs / task registration 分離 |
|
||||
| 1237 | `app.py` | P1 bootstrap | 保持只做 app setup;繼續往 app_factory / extension setup 抽;Phase 42 只做 metadata table name 對齊 |
|
||||
| 2149 | `services/elephant_alpha_autonomous_engine.py` | P1 ElephantAlpha engine | HITL / executor / planning policy |
|
||||
| 970 | `routes/cicd_routes.py` | P2 CI/CD Blueprint | route glue / CI query service / deployment action service |
|
||||
| 1250 | `run_scheduler.py` | P2 scheduler entrypoint | observability jobs / token report jobs / task registration 分離 |
|
||||
| 916 | `services/ppt_auto_generation_service.py` | P2 PPT 自動產線 service | schedule resolver / generation queue / missing report planner |
|
||||
| 966 | `services/trend_crawler.py` | P2 crawler service | source adapters / parser / persistence |
|
||||
| 942 | `services/learning_pipeline.py` | P2 RAG learning pipeline | distiller / promotion gate / persistence / telemetry |
|
||||
| 868 | `services/import_service.py` | P2 import service | validators / import writers / report builders |
|
||||
| 940 | `services/import_service.py` | P2 import service | validators / import writers / report builders |
|
||||
| 1071 | `services/telegram_templates.py` | P2 Telegram templates | alert template groups / channel-specific formatting / reusable render helpers |
|
||||
| 867 | `services/token_report_service.py` | P2 token report service | query / aggregation / chart payload / notification formatting |
|
||||
| 4865 | `services/marketplace_product_matcher.py` | P2 marketplace matcher | identity parsing / unit-comparable scoring / search term quality / persistence normalization |
|
||||
| 865 | `routes/daily_sales_routes.py` | P2 Daily Sales Blueprint | route glue / export helpers / daily query and formatting service |
|
||||
| 844 | `services/ollama_service.py` | P2 Ollama client | host health / request client / fallback policy / response parsing |
|
||||
| 832 | `routes/export_routes.py` | P2 Export flow | export command/router glue / file path / download orchestration |
|
||||
| 809 | `services/competitor_price_feeder.py` | P2 competitor price feeder | crawler scheduling / price normalization / cache strategy |
|
||||
| 1266 | `services/ollama_service.py` | P2 Ollama client | host health / request client / fallback policy / response parsing |
|
||||
| 849 | `services/pchome_crawler.py` | P2 PChome crawler | search fetch / parsing / fallback source handling / rate limit policy |
|
||||
| 1100 | `services/code_review_pipeline_service.py` | P2 Code review pipeline service | scan orchestration / finding normalization / persistence adapter |
|
||||
| 953 | `routes/export_routes.py` | P2 Export flow | export command/router glue / file path / download orchestration |
|
||||
| 816 | `services/ppt_vision_service.py` | P2 PPT vision QA service | runtime state / queue status / model probe / audit execution 分離 |
|
||||
| 2149 | `services/competitor_price_feeder.py` | P2 competitor price feeder | crawler scheduling / price normalization / retryable candidate recovery / cache strategy |
|
||||
| 1535 | `services/competitor_intel_repository.py` | P2 competitor intel repository | review queue query / cache shaping / formatting helpers |
|
||||
| 805 | `routes/bot_api_routes.py` | P2 Bot API Blueprint | route glue / bot action service |
|
||||
| 1319 | `routes/market_intel_review_report_routes.py` | P2 market intel review report Blueprint | review report route glue / export payload / phase handoff orchestration |
|
||||
| 917 | `routes/market_intel_routes.py` | P2 market intel Blueprint | page route / API route glue / MCP gate route registration helper |
|
||||
| 1965 | `services/market_intel/deployment_readiness.py` | P2 market intel deployment readiness | preflight gates / readiness payload / route contract helpers |
|
||||
| 846 | `services/market_intel/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_receipt.py` | P2 market intel review receipt pipeline | AI summary / persistence / Telegram dispatch / report catalog run receipt orchestration |
|
||||
|
||||
## 市場情報開發前置禁區
|
||||
|
||||
|
||||
338
docs/memory/current_execution_queue_20260524.md
Normal file
338
docs/memory/current_execution_queue_20260524.md
Normal file
@@ -0,0 +1,338 @@
|
||||
# Current Execution Queue 2026-05-24
|
||||
|
||||
> 目的:把目前使用者要求的核心產品優化整理成單一執行佇列,避免 UI、比價、PPT、AI 觀測台與部署驗證漏項。
|
||||
> 原則:準確率優先;不放寬 MOMO / PChome 全域比對門檻;正式部署不碰 `momo-db`;每個工作流都需有測試或正式 smoke。
|
||||
|
||||
## 0. 部署與驗證通道基準
|
||||
|
||||
- 確認 `main`、Gitea、正式環境版本一致。
|
||||
- 修復或確認 SSH / Gitea / 188 hop 可用。
|
||||
- 每次上線只 recreate `momo-app`、`scheduler`、`telegram-bot`,禁止使用 `--remove-orphans`,禁止影響 `momo-db`。
|
||||
- 2026-05-24 21:33 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.451`。本輪只 recreate `momo-app`;`scheduler`、`telegram-bot` 未重建但保持 healthy;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:主要頁面 HTTP 200、三個 app 容器 healthy、`/api/pchome-review/queue` 可用於 `recoverable_low_score` / `legacy_low_score` read-only 查詢,且 10 分鐘錯誤 log 未見 Traceback / ERROR。
|
||||
- 2026-05-24 22:17 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.453`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、Gemini hard disabled 且 24 小時 `ai_calls` 無 Gemini provider、Ollama 順序維持 GCP-A → GCP-B → 111、`/api/pchome-review/queue` 三個 status 查詢成功、rescore audit read-only `selection_mode=latest_sku_only`。
|
||||
- 2026-05-24 22:55 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.455`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、EventRouter `decision_envelope` 直送不進 L1/L2 AI handler、Telegram 信封顯示標的 SKU 與 PChome 候選、Gemini hard disabled 且 24 小時 `ai_calls` 無 Gemini provider、Ollama 順序維持 GCP-A → GCP-B → 111、`/api/pchome-review/queue?review_status=rescore_accepted` 查詢成功、10 分鐘錯誤 log 未見 Traceback / ERROR / CRITICAL。已執行 `--retract-variant-accepted`,最新 `rescore_accepted_current` 中 `variant_selection_review` 殘留為 0。
|
||||
- 2026-05-24 23:05 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.456`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、`/api/pchome-review/queue?review_status=rescore_accepted` 每筆帶 `decision_envelope`、guardrail `can_auto_execute=false`、Gemini hard disabled 且 24 小時 `ai_calls` 無 Gemini provider、Ollama 順序維持 GCP-A → GCP-B → 111、5 分鐘三容器錯誤 log 未見 Traceback / ERROR / CRITICAL。
|
||||
- 2026-05-24 23:17 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.457`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、Dashboard PChome 覆核頁顯示 `dashboard-review-envelope` 與 HITL、`/api/pchome-review/queue?review_status=rescore_accepted` 仍帶 `decision_envelope` 且 `can_auto_execute=false`、Excel flatten helper 輸出決策信封 ID/資料品質/自動執行允許/證據摘要、Gemini hard disabled 且 24 小時 `ai_calls` 無 Gemini provider、Ollama 順序維持 GCP-A → GCP-B → 111、5 分鐘三容器錯誤 log 未見 Traceback / ERROR / CRITICAL。
|
||||
- 2026-05-24 23:30 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.458`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、`summarize_review_decision_envelopes()` 產生 3 條 HITL 摘要且全數 `can_auto_execute=false`、`_fetch_competitor_summary()` 帶 `review_decision_text`、`/api/pchome-review/queue?review_status=rescore_accepted` 仍帶 `decision_envelope` 且 `can_auto_execute=false`、核心頁面 `/daily_sales`、`/growth_analysis`、`/observability/ppt_audit_history`、`/market_intel/matches`、`/vendor-stockout/list` HTTP 200;5 分鐘三容器錯誤 log 未見 Traceback / ERROR / CRITICAL,最新 `rescore_accepted_current` 中 `variant_selection_review` 殘留為 0。
|
||||
- 2026-05-24 23:43 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.459`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、`/api/pchome-review/queue?review_status=protected_existing_match` 輸出 `existing_match_conflict`,建議動作為 `compare_existing_identity` 且 `can_auto_execute=false`、Dashboard protected filter / daily / growth / PPT audit HTTP 200;5 分鐘三容器錯誤 log 未見 Traceback / ERROR / CRITICAL。
|
||||
- 2026-05-24 23:59 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.460`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、`scripts/check_sales_charts_runtime.js --base-url https://mo.wooo.work --routes /daily_sales,/growth_analysis` 通過,daily/growth 主要 canvas 均有 Chart.js instance、非零資料點與彩色像素;核心頁面 `/daily_sales`、`/growth_analysis`、`/observability/ppt_audit_history`、PChome protected queue API HTTP 200;5 分鐘三容器錯誤 log 未見 Traceback / ERROR / CRITICAL。
|
||||
- 2026-05-25 08:12 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.461`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、Dashboard template 已顯示「尚未搜尋」與「尚未進入 PChome 補抓」、Gemini hard disabled 且 24 小時 `ai_calls` 無 Gemini provider、Ollama 順序維持 GCP-A → GCP-B → 111;5 分鐘三容器錯誤 log 未見 Traceback / ERROR / IntegrityError。
|
||||
- 2026-05-25 08:18 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.462`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、Dashboard / AI 中樞 / API / 前端 confirm 均改用「PChome 補抓產線 / 補抓未搜尋 / 未搜尋補抓」、Gemini hard disabled 且 24 小時 `ai_calls` 無 Gemini provider、Ollama 順序維持 GCP-A → GCP-B → 111;5 分鐘三容器錯誤 log 未見 Traceback / ERROR / IntegrityError。
|
||||
- 2026-05-25 08:38 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.464`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、`/`、`/?filter=pchome_review`、`/daily_sales`、`/growth_analysis`、`/observability/ppt_audit_history`、PChome rescore queue API HTTP 200。DR.WU 三筆 SKU read-only rescore 全數 `gate_pass=3/3`,`--apply-accepted` 後 latest 狀態為 `rescore_accepted_current`、`best_match_score=1.0`、`price_basis=total_price`;整體 latest counts 變為 `true_low_confidence=778`、`rescore_accepted_current=34`。5 分鐘 log 未見 Traceback,但有既有 `[Embed] all hosts failed` 錯誤,需列入下一輪 Ollama embedding 健康檢查。
|
||||
- 2026-05-25 10:04 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.465`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、`/`、`/daily_sales`、`/growth_analysis`、`/observability/ppt_audit_history`、PChome rescore queue API HTTP 200;容器內 routing smoke 證明 resolver 回 111 且 `allow_111_fallback=false` 時會改試 GCP-A/GCP-B,輸出 `tried=['http://34.87.90.216:11434','http://34.21.145.224:11434']`;真實短 embedding 在 GCP-A `/api/version` timeout、GCP-B 200 情境下成功回 1024 維向量,耗時 4.59 秒。3 分鐘三容器錯誤 log 未見 Traceback / ERROR / CRITICAL。
|
||||
- 2026-05-25 12:10 CST 狀態:已部署 `V10.467` 到 188,正式 `/health` 為 `V10.467`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、`/`、`/daily_sales`、`/growth_analysis`、`/observability/ppt_audit_history`、PChome rescore queue API HTTP 200。Production pilot 將 9 筆 focused exact total-price SKU 追加為 `rescore_accepted_current`,整體 latest counts 從 `true_low_confidence=802` / `rescore_accepted_current=38` 變為 `true_low_confidence=793` / `rescore_accepted_current=47`;目標 SKU 的 `competitor_prices` 最新 `crawled_at` 仍停在 2026-05-22~2026-05-23,確認本輪未寫正式價差表。已知後續:GCP-A / GCP-B Ollama `/api/version` 目前連線失敗,背景 embedding 正確沒有落 111,但 app/scheduler log 仍會出現 `[Embed] all 2 hosts failed`,需另開 Ollama 健康處理。
|
||||
- 2026-05-25 12:27 CST 狀態:已部署 `V10.468` 到 188,正式 `/health` 為 `V10.468`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、`/`、`/daily_sales`、`/growth_analysis`、`/observability/ppt_audit_history`、PChome review queue API `/api/pchome-review/queue` HTTP 200;容器內 mock smoke 證明背景 embedding 在 GCP-A / GCP-B 全失敗後會開啟 60 秒 failure circuit,第二筆不再重複打兩台 GCP,且不落 111。GCP 維運盤點:GCP-A `22/11434` refused;GCP-B `22` open 但現有 key publickey denied,部署 smoke 時 GCP-B `11434` 已恢復 200、`get_ollama_host()` 選到 GCP-B;111 `/api/version` 可用,但 111 仍不得承接背景 `bge-m3`。
|
||||
- 2026-05-25 12:39 CST 狀態:已部署 `V10.469` 到 188,正式 `/health` 為 `V10.469`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、首頁 / daily / growth / PChome review queue HTTP 200、Gemini hard disabled;`allow_111_fallback=False` 時 GCP-only embedding 全失敗會開啟 failure circuit 並記 WARNING,不再把預期內的背景熔斷打進 ERROR 通道。觀測到 GCP-B `/api/version` 200,但 `/api/embed` 仍可能 15s timeout,下一步需修 GCP-A primary 與 GCP-B runner/model 負載。
|
||||
- 2026-05-25 12:53 CST 狀態:已部署 `V10.470` 到 188,正式 `/health` 為 `V10.470`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三容器 healthy、host health page HTTP 200 並顯示 Runtime 狀態、scheduler probe 寫入 DB。最新 `host_health_probes`:GCP-A unhealthy(11434 refused)、GCP-B unhealthy(`EmbedProbe ReadTimeout`, `/api/tags` 仍可列出 4 模型)、111 healthy;這補上「HTTP API 活著但模型 runtime 卡住」的假健康監控缺口。
|
||||
- 2026-05-25 13:38 CST 狀態:已部署 `V10.471` 到 188,正式 `/health` 為 `V10.471`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三容器 healthy、首頁 / daily / growth / host_health / ppt_audit_history / PChome review queue HTTP 200。GCP-B `bge-m3` `/api/embed` 直接實測約 6.4s、7.3s、23.5s,原 `OLLAMA_EMBED_MAX_TIMEOUT=15` 與 host health `OLLAMA_HOST_HEALTH_EMBED_TIMEOUT=8` 會誤殺慢但成功的 embedding;預設改為 30s。正式容器內 embedding smoke 回 1024 維、耗時 10.07s;手動 host health probe 後最新狀態為 GCP-A unhealthy、GCP-B healthy、111 healthy。背景 embedding 路由安全不變:GCP-A → GCP-B,不落 111。
|
||||
- 2026-05-25 14:10 CST 起,`V10.472` 補 rootless GCP Ollama failover 診斷腳本與 DevOps SOP:`scripts/ops/diagnose_ollama_gcp_failover.sh` 會檢查 direct GCP-A/GCP-B/111、110 proxy `11435/11436` 與 GCP-B `bge-m3` runtime。現況輸出:GCP-A direct `/api/version` failed/refused、GCP-B direct OK、111 OK、110:11435 502、110:11436 OK、GCP-B embed OK;110 無免密 sudo,`ssh gcp-a` 22 refused、`ssh gcp-b` publickey denied,因此 primary 修復需 GCP/SSH 或 110 root 權限。
|
||||
- 2026-05-25 14:12 CST 起,`V10.473` 進行背景 embedding host_health skip:`allow_111_fallback=false` 的背景 embedding 會讀最近 `host_health_probes`,若 GCP-A/GCP-B runtime 已被標 unhealthy,直接跳過該節點並開 GCP circuit,不等待 30 秒 timeout、不落 111;DB 讀取失敗 fail-open。
|
||||
- 2026-05-25 14:45 CST 起,`V10.474` 補 PChome near-threshold matcher / feeder 下一階段:HOOOME 白色經典香氛暖燈、Gdesign Aroma Lava 2.0 進 total-price exact;Recipe Box 可撕式水性兒童指甲油保留 identity_review,不自動寫正式價差;Pavaruni 蠟燭 vs 精油與 DASHING DIVA 不同款式仍不放行。known-id refresh 會對 hard-veto 舊候選跑 fresh search recovery;missing known-id 若 fresh search 只有低分候選,也保留 best candidate + diagnostics,不再只記 `refresh_no_result`;正式覆寫保護新增 stronger existing guard。
|
||||
- 2026-05-25 15:20 CST 起,`V10.475` 補 rescore CLI 與高分錯配防線:audit CLI 預設不再只掃 `strong_exact_spec_match`,避免新版 `focused_exact_*` 理由漏掃;matcher 對香氛暖燈 S/M/L 尺寸差、NITORI 香氛噴霧器型號差直接 hard veto,彩妝色號單邊出現時送 `variant_selection_review`,避免高分但不同 variant 的候選被誤推入 accepted queue。
|
||||
- 2026-05-25 16:15 CST 起,`V10.476` 補商業條件差防線:即期品、效期/保存期限、盒損、福利品等條件只出現在單側時,matcher 加 `commercial_condition_gap` 並送 `variant_selection_review`,不讓同名但商品狀態不同的候選自動進 accepted queue。
|
||||
- 2026-05-25 19:20 CST 起,`V10.477` 補高分錯配防線:SPF 數值不同直接 veto,MAKE UP FOR EVER 定妝噴霧 vs 活氧水不同線直接 veto;多款任選對單一款與單側色號改送 `variant_selection_review`,涵蓋私密潔浴露、身體去角質、美體乳液與染眉膏等。
|
||||
- 2026-05-25 21:05 CST 起,`V10.480` 補 accepted-current 風險樣本防線:rom&nd 零絲絨/果凍唇釉 vs 果汁唇釉多款 listing 直接 `romand_lip_line_conflict` hard veto;Relove 潔淨凝露若傳明酸/淨白活性只出現在單側,保留高分但進 `variant_selection_review`;1990 融燭燈不同設計(歐式可彎 vs 韓風原木底座)直接 `selection1990_wax_lamp_design_conflict` hard veto。
|
||||
- 2026-05-25 21:25 CST 起,`V10.481` 補 rescore accepted retraction 工具缺口:退回工具會用當前 matcher 重判 latest `rescore_accepted_current`,凡新版已變 `variant_selection_review / low_score_current` 的舊 accepted 會追加退回 `true_low_confidence`,避免人工覆核隊列保留舊版安全閘門放行的候選。Production 已部署 `/health=V10.481`;保守 materialize 15 筆安全 SKU 後再退回 7 筆舊 accepted 變體風險,最新 accepted audit 為 `scanned=89 / gate_pass=89 / still_low=0`。
|
||||
- 2026-05-25 21:47 CST 起,`V10.482` 補 exact variant-safe 回收:LUSH 櫻之花身體噴霧 200ml、ARTMIS 金縷梅/蔓越莓私密清潔慕斯 250ml、SO NATURAL FIXX 120ml plain 與 Baan 原味/草莓同 catalog,若雙方同品名、同規格且同明確 variant,移除過度保守的 `variant_selection_review` 並進 `exact / total_price / price_alert_exact`;SO NATURAL 經典款/光澤款/霧面款/夏日款 catalog 對單款 120ml 仍維持人工覆核。Production 已部署 `/health=V10.482`,只 materialize 5 筆新增 exact-line SKU 到 `rescore_accepted_current`;latest accepted audit 為 `scanned=94 / gate_pass=94 / still_low=0`,三應用容器 healthy、`momo-db` 未 recreate。
|
||||
- 2026-05-25 23:03 CST 起,`V10.483` 收斂舊 gate pass 風險:NARS 遮瑕蜜任選、LOREAL 玻尿酸啵啵精華水/液態紫熨斗 vs 水光精華、SEBAMED 洗髮乳任選、Schick 舒綺 2-in-1 型號落差、TAICEND 保護膜 vs 噴霧,現在都會保留高分但加 `variant_selection_review` 與專屬 reason,不再被 rescore 自動送進 accepted queue。Production 已部署 `/health=V10.483`;目標 5 SKU audit `gate_pass=0 / still_low=5`,並用 `--retract-variant-accepted` 退回 4 筆舊 accepted 變體風險,latest accepted audit 為 `scanned=90 / gate_pass=90 / still_low=0`。
|
||||
- 2026-05-25 12:05 CST 狀態:`main` 已部署到 188,正式 `/health` 為 `V10.467`,待推 Gitea。兩段變更已合併驗證:V10.466 rescore duplicate 改看 latest-state,7 筆 SKU 最新 attempt 全為 `rescore_accepted_current`,`competitor_prices` / `competitor_price_history` 目標計數未變;V10.467 focused exact matcher 在容器內回 `exact / total_price / price_alert_exact`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三容器 healthy、PChome rescore queue API HTTP 200、Gemini 24 小時無 provider 紀錄、Ollama env 順序維持 GCP-A → GCP-B → 111、3 分鐘三容器 log 未見 Traceback / ERROR / CRITICAL / IntegrityError。
|
||||
|
||||
## 1. MOMO / PChome 核心比價準確率
|
||||
|
||||
- 查正式 `competitor_match_attempts` 最新狀態分布與高量低信心 cohort。
|
||||
- 以小批次 pilot 處理 `recoverable_low_score`,優先品線:
|
||||
- DASHING DIVA
|
||||
- aroma / diffuser / essential oil
|
||||
- lip / cosmetic variant
|
||||
- private-care / body-care
|
||||
- 2026-05-24 22:10 CST 起,PChome rescore audit 預設對齊 review queue 最新狀態:先取每個 SKU 最新 attempt,再套用 status / reason 篩選;歷史候選回看需明確使用 `--include-historical-candidates`。
|
||||
- 2026-05-24 22:20 CST 起,matcher replay 先套用 V10.453 安全修正:`EX8` 型號不視為 `x8` 入數,香氛固體凝膠一側泛稱、一側具體香味/No. 款式走 veto;Herbacin 小甘菊護手霜 20ml brandless 可作窄範圍安全回收。
|
||||
- 2026-05-24 22:42 CST 起,feeder / rescore audit 套用 V10.454 安全閘門:`identity_review` / `manual_review` / `variant_selection_review` 的近門檻候選只能留在覆核,不能由 replay、refresh 或 `accepted_current` 入隊語意自動寫正式 PChome 價差。
|
||||
- 2026-05-24 22:48 CST 已執行 production rescore 入隊:745 筆 `true_low_confidence` 中先有 2 筆通過舊 gate;V10.454 gate 補上 `variant_selection_review` 排除後,SKU `8884618` KATE 多款任選唇膏已退回最新 `true_low_confidence`,最終只保留 SKU `10922465` Herbacin 小甘菊護手霜 20ml 為 `rescore_accepted_current` 人工覆核 attempt;正式價格表未寫入,Dashboard / competitor intel cache 已清除。
|
||||
- 2026-05-24 22:44 CST 起,rescore audit 補 `--retract-variant-accepted` 工具化退回路徑;若最新 `rescore_accepted_current` 仍帶 `variant_selection_review`,只追加 `true_low_confidence` attempt,不刪歷史、不寫正式價格表。
|
||||
- 只新增窄範圍、可解釋 matcher 規則。
|
||||
- 保留 `MIN_MATCH_SCORE`、`identity_veto`、既有正式候選覆寫保護。
|
||||
- 驗收:`matched` 有增加、目標 `low_score` 下降、`needs_review` 不異常上升、無明顯跨色號/跨款式/跨劑型錯配。
|
||||
|
||||
## 2. 商品列表與人工覆核閉環
|
||||
|
||||
- 商品列表不得再大量顯示籠統「待對比」。
|
||||
- 將狀態拆成:尚未搜尋、價格過期待刷新、近門檻可救回、證據不足、既有強配對保護、已排除、需單位價比較、找不到同款。
|
||||
- 每筆覆核要顯示候選 PChome 商品、候選價、match score、診斷原因、下一步動作。
|
||||
- 人工採用 / 否決 / 單位價 / 補搜尋必須能回寫 review queue,並影響 feeder 後續行為。
|
||||
- 2026-05-25 00:15 CST 起,PChome 補抓操作入口同步收斂為「PChome 補抓產線 / 補抓未搜尋 / 未搜尋補抓」,包含 Dashboard、AI 中樞、前端 confirm 與 API 回覆,不再讓操作入口殘留籠統「待比對」。
|
||||
- 2026-05-25 00:06 CST 起,尚未進入 PChome 搜尋/補抓的商品列表文案改為「尚未搜尋」與「尚未進入 PChome 補抓」,不再顯示籠統「待比對」,避免和低信心待人工覆核混淆。
|
||||
- 2026-05-24 23:40 CST 起,`protected_existing_match` 的 review `decision_envelope` 會解析 `existing_match_conflict`,列出既有正式候選、新候選、雙方 score 與 delta;這類案件仍不可自動覆蓋正式價差,但人工覆核、Agent 與 PPT 不再只看到籠統「既有保護」。
|
||||
|
||||
## 2.1 近門檻 / 高信心待審 matcher 補強
|
||||
|
||||
- 2026-05-25 08:30 CST 起,rescore audit 支援 `--sku` repeatable 精準篩選;production pilot 可只指定 3-10 個 SKU 執行 read-only audit 或 `--apply-accepted`,避免寬範圍掃描誤把不同 cohort 混在同一次驗證。
|
||||
- 2026-05-25 08:25 CST 起,`DR.WU / DR WU / DRWU / 達爾膚` 視為同一品牌 alias;正式樣本中的 DR.WU 玻尿酸保濕精華乳 50ML、2入組與杏仁酸亮白煥膚精華 18% 30ML 2入組,在不調整全域門檻下可由 brandless identity review 回到 exact total-price lane。
|
||||
- 2026-05-25 08:36 CST production pilot:SKU `10362820`、`10653216`、`10653329` 已從 `true_low_confidence` materialize 為 `rescore_accepted_current`,只進人工覆核隊列,不寫 `competitor_prices`。
|
||||
- 2026-05-25 11:55 CST 起,rescore audit duplicate 判斷只看最新 attempt;若歷史已有 accepted 但後續 crawler 又追加低信心列,可重新 materialize 成最新 `rescore_accepted_current`。Production pilot 已將 SKU `14756069`、`11159042`、`13842560`、`8394210`、`15192547`、`10509765`、`10603780` 入人工覆核隊列;正式 `competitor_prices` / `competitor_price_history` 未寫入或改變。
|
||||
- 2026-05-25 12:20 CST 起,matcher 新增 `focused_exact_total_price_safe` 窄範圍通道;目前只覆蓋 3W CLINIC 粉底液 2入、花美水凝膠 3支、The Ordinary 咖啡因 EGCG 30ml、KUSSEN 屁屁膏 3入、Bone 擴香禮盒、1990 融燭燈白色款與 CANMAKE 淚袋盤等已確認同款樣本。這讓高信心 `exact/manual_review` 能轉為 `exact/total_price` 供 rescore pilot 入人工覆核;DASHING DIVA、唇彩、香味、色號/款式敏感商品仍不放行。
|
||||
- 2026-05-25 12:25 CST production pilot:SKU `6101639`、`10074951`、`7760902`、`TP00074980000005`、`14774766`、`10142589`、`10262470`、`10262471`、`11308520` 已從 `true_low_confidence` materialize 為 `rescore_accepted_current`,全數 `exact/total_price/price_alert_exact` 且理由含 `focused_exact_total_price_safe`。SKU `6101784` 因「即期品」商業條件不同,刻意保留在 `true_low_confidence`,不納入本輪自動入隊。
|
||||
- 2026-05-25 14:45 CST 起,matcher 擴充至香氛/精油近門檻安全 cohort:HOOOME 白色經典香氛暖燈與 Gdesign Aroma Lava 2.0 可進 `exact/total_price/price_alert_exact`;Recipe Box 可撕式水性兒童指甲油只進 `identity_review`,因兒童指甲油仍可能藏色款/款式。DASHING DIVA 與 Pavaruni cross-type 負例已補測試,避免跨款式、跨劑型誤配。
|
||||
- 2026-05-25 15:20 CST 起,新增三個正式觀察到的高分負例防線:PRAY 守夜人暖燈 L vs S、NITORI 香氛噴霧器 5510 vs YX168、LA MER 氣墊粉霜通用 listing vs `11 Rosy Ivory` 色號。前兩者 hard veto,後者保留高分但不進 accepted queue。
|
||||
- 2026-05-25 16:15 CST 起,新增商業條件差負例:KAMERIA 足膜、3W CLINIC 粉底液、Sisley 全能乳液等若一側標示即期/效期/盒損,仍可顯示高相似度,但只進 identity review,不自動入 accepted queue。
|
||||
- 2026-05-25 19:20 CST 起,新增高分負例:Jealousness SPF25 vs SPF50、MAKE UP FOR EVER 超光肌控油定妝噴霧 vs 超光肌活氧水、rom&nd 染眉膏通用 listing vs `03 摩登米`、Lactacyd 多款潔浴露 vs 單一亮肌柔滑、我的心機多款身體去角質 vs 單一香型。前兩者 hard veto,其餘進 identity review。
|
||||
- 2026-05-25 20:05 CST 起,新增高分錯配 / catalog 變體防線:AUS LIFE 檸檬草 vs 茶樹滾珠精油、NOW 椰子油膏 vs 乳木果油、港香蘭漢本 vs 艾魔菈爽身粉改為 hard veto;多色 / 多香 / 數字區間 catalog 對單一款式(KATE 粉餅盒、植村秀眉筆、PERIPERA 01~07 眼線膠筆、Jo Malone 車用擴香蕊芯等)只進 `variant_selection_review`。
|
||||
- 2026-05-25 20:35 CST 起,依 production audit 續補二階風險:同規格但一側為潔膚露、一側為修護乳/乳液直接 `cleanser_lotion_line_conflict` hard veto;私密防護慕絲多款可選 vs 單一香型、滋養霜單側清爽型只進 `variant_selection_review`。
|
||||
- 2026-05-25 21:05 CST 起,依 accepted-current 二次抽樣續補三類風險:rom&nd 唇釉不同品線不可互收;Relove 潔淨凝露傳明酸/淨白變體不可自動 accepted;1990 融燭燈同色但不同燈座/結構不可互收。下一步先部署 V10.480,再回查 accepted-current 是否仍有上述 4 筆風險 SKU。
|
||||
- 2026-05-25 21:25 CST 起,accepted queue 清潔不再只靠舊 `diagnostic_codes`:`--retract-variant-accepted` 改為先抓 latest accepted,再用當前 matcher 判斷是否需要退回。這能清掉 V10.480 後才被新規則判為 `variant_selection_review` 的舊 accepted。正式最新狀態:`true_low_confidence=751`、`rescore_accepted_current=89`、`identity_veto=3994`、`matched=1570`、`unit_comparable=379`。
|
||||
- 2026-05-25 23:45 CST 起,`V10.484` 拆分 manual gate exact 與型錄風險:POWERMAN 男性私密養護液 30ml、PHYSIOGEL AI 冰鎮精華露 200ml 2入、TS6 緊彈水嫩凝膠 40g、DERMA 寶寶洗髮沐浴露 150/500ml、Clarins 黃金亮眼萃 20ml、Cetaphil 長效潤膚乳 237/473ml 等明確同款可走 `exact / total_price / price_alert_exact`;COCODOR 大豆蠟燭單側多款任選保留 `variant_selection_review`,Pavaruni 雙側 20 香味蠟燭保持 total-price exact。測試:`tests/test_marketplace_product_matcher.py`、`tests/test_competitor_match_attempts_persistence.py`、`tests/test_competitor_match_attempt_rescore_audit.py` 通過。
|
||||
- 2026-05-25 23:55 CST 起,`V10.485` 補 NITORI 香氛噴霧器短型號防線:near-threshold read-only pilot 中唯一 gate pass 為 5510 vs J82 LBR,已判定不該入隊;matcher 將 `J82` 這類短英數型號納入 NITORI diffuser model conflict,與 5510 / YX168 等不同型號一樣 hard veto。Production 已部署 `/health=V10.485`;120 筆 near-threshold audit 由 `gate_pass=1` 變 `gate_pass=0`,accepted audit `scanned=89 / gate_pass=89 / still_low=0`。
|
||||
- 2026-05-29 起,`V10.486` 補 PChome near-threshold 風險邊界:NEW DIRECTIONS 甜杏仁油 vs 酪梨油直接 hard veto;COCODOR 經典擴香瓶多款任選、KAMERIA 足膜任選三款、Hakugen 白元入浴劑橘盒/綠盒不同變體都保留 `variant_selection_review`,不進可採用 gate。Production 已部署 `/health=V10.486`;240 筆 near-threshold audit `gate_pass 83→79`、`identity_veto 0→1`、`still_low 157→160`。
|
||||
- 2026-05-29 起,`V10.488` 新增市場情報 MCP Fetch Run Receipt 安全預覽 gate,只審核操作員 dry-run receipt,不執行 CLI、不抓外站、不寫 DB。
|
||||
- 2026-05-29 起,`V10.489` 補 PChome 低分同款人工覆核回收與 gate-pass 風險邊界:TS6 超美白香氛誘霜 120g/ml、W 修護保養蝸牛特潤修護面膜 6 片、Derma 大地 Eco 植萃護膚油 2 入,從低信心升成 `identity_review` 候選;Clarins 輕盈美體護理油 vs 身體調和護理油、台塑生醫嬰兒沐浴/洗髮組合數量反轉、isLeaf 私密慕絲香型數量不一致改 hard veto;HOOOME 大理石暖燈 vs 泛稱經典款只留 `variant_selection_review`,不進 total-price accepted。Production 已部署 `/health=V10.489`;500 筆 read-only audit 由 V10.486 基線 `gate_pass=129 / identity_veto=1 / still_low=370` 收斂為 `gate_pass=124 / identity_veto=4 / still_low=372`,三應用容器 healthy、`momo-db` 未 recreate。
|
||||
- 2026-05-31 起,`V10.490` 補 ElephantAlpha / OpenClaw 舊策略 action 相容:正式日誌觀察到 `agent=openclaw action=generate_market_strategy` 被當未知步驟丟錯;此類 OpenClaw strategy 產生步驟已定義為 advisory skipped,只記 warning,不觸發 circuit breaker、不執行外部策略、不影響價格行動。測試覆蓋 `generate_market_strategy` 與 `generate_resource_optimization_strategy`,未知 action 仍維持 fail-fast。
|
||||
- 2026-05-31 起,`V10.491` 新增市場情報 MCP Fetch Result Parser Review gate:在 receipt gate 後只審核操作員 shell parser 貼回的結構化摘要,對齊 source/path、公開 URL、候選必要欄位、小批次上限、raw HTML/secret/side-effect 風險;仍不讀 artifact、不執行 CLI、不連外、不寫 DB、不掛 scheduler。
|
||||
- 2026-05-31 起,`V10.492` 收緊 PChome 近門檻自動回刷:`run_retryable_candidate_revalidation()` 只回刷 `recoverable_low_score` 與 legacy `low_score / refresh_low_score`,且 SQL 端要求 `hard_veto=false`、`comparison_mode=exact_identity`、diagnostic reasons 命中同品線/identity anchor;`identity_veto`、`unit_comparable`、`true_low_confidence` 不再進每日自動回刷隊列,需等待新證據或人工處理。
|
||||
- 2026-05-31 起,`V10.493` 新增市場情報 MCP Fetch Candidate Handoff Review gate:在 parser review 通過後只審核候選交接包,要求 source/candidate key 完全對齊、queue policy 維持 manual preview、小批次上限與操作員無寫入/無連外/無排程確認;仍不建立 queue、不寫 DB、不讀 artifact、不連外、不掛 scheduler。
|
||||
- 2026-05-31 起,`V10.494` 新增市場情報 MCP Fetch Candidate Queue Review gate:在 handoff review 通過後只審核人工 queue review 草案,要求候選 key 完全對齊、review_state 只停在 `needs_review`、allowed actions 限人工操作、queue_write_status 維持 `not_persisted`;仍不建立 queue、不更新 review_state、不寫 DB、不連外、不掛 scheduler。
|
||||
- 2026-05-31 起,`V10.495` 新增市場情報 MCP Fetch Candidate Queue Writer Preflight gate:在 queue review 通過後只審核 writer preflight 草案,要求 target table、write mode、dedupe strategy、insert columns、payload rows 與候選 key 完全對齊;仍不開 DB、不執行 CLI、不建立 queue、不更新 review_state、不寫 DB、不連外、不掛 scheduler。
|
||||
- 2026-05-31 起,`V10.496` 新增市場情報 MCP Fetch Candidate Queue Writer CLI Review gate:在 writer preflight 通過後只審核 CLI review 草案,要求 script path、target table、preflight id、row count、candidate/dedupe keys 與 read-only command argv 對齊;仍不執行 CLI、不讀 approval token、不寫檔、不開 DB、不寫 queue、不掛 scheduler。
|
||||
- 2026-05-31 起,`V10.497` 新增市場情報 MCP Fetch Candidate Queue Writer Run Package Review gate:在 CLI review 通過後只審核 operator run package 草案,要求 artifact manifest、operator shell command sequence、package identity、candidate/dedupe keys 與 CLI review 對齊;仍不產檔、不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不掛 scheduler,只放行到 run readiness review。
|
||||
- 2026-05-31 起,`V10.498` 新增市場情報 MCP Fetch Candidate Queue Writer Run Readiness gate:在 run package review 通過後只審核 operator readiness 證據,要求 run readiness artifact、reviewed sample、備份、read-only preflight 與 post-write smoke 路徑安全,並確認 CLI-only、approval token shell-only、無 API/DB/file/scheduler 副作用;仍不產檔、不讀 token、不執行 CLI、不開 DB、不寫 queue、不掛 scheduler,只放行到後續 run receipt review。
|
||||
- 2026-05-31 起,`V10.499` 新增市場情報 MCP Fetch Candidate Queue Writer Run Receipt Review gate:在 run readiness 通過後只審核操作員 shell writer run 的 receipt 摘要,要求 readiness linkage、run package id、候選/dedupe keys、writer output、post-write smoke、backup path 與 operator confirmation 對齊;仍不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-write query、不掛 scheduler,只放行到 closeout review。
|
||||
- 2026-05-31 起,`V10.500` 新增市場情報 MCP Fetch Candidate Queue Writer Run Closeout Review gate:在 receipt review 通過後只審核 operator closeout 摘要,要求 receipt linkage、closeout artifact、receipt review artifact、post-closeout inventory plan、writer output / post-write smoke / backup manifest、rollback note 與 operator confirmation 對齊;仍不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-closeout query、不掛 scheduler,只放行到 read-only post-closeout inventory review。
|
||||
- 2026-05-31 起,`V10.501` 新增市場情報 MCP Fetch Candidate Queue Writer Post-Closeout Inventory Review gate:在 closeout review 通過後只審核 operator live inventory read-only 摘要,要求 closeout linkage、row count、inventory artifact、closeout review artifact、read-only query result、missing/duplicate rows 與 operator confirmation 對齊;仍不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 inventory query、不掛 scheduler,只放行到 candidate queue review handoff。
|
||||
- 2026-05-31 起,`V10.502` 修正 AiderHeal 自動修復診斷鏈:先檢查 ADR-020 檔案白名單再執行 110 preflight,`tests/` finding 會明確略過而不誤報 repo preflight;Code Review 完成通知會把全數不在白名單的 finding 標成需人工處理,不再宣稱已觸發 AiderHeal;白名單放行 `services/routes/database` 子目錄 Python 檔,preflight 通知帶遠端錯誤細節,健康檢查接受 `/health` 回 `healthy`。
|
||||
- 2026-05-31 起,`V10.503` 新增市場情報 MCP Fetch Candidate Queue Writer Review Handoff gate:在 post-closeout inventory review 通過後只審核 operator candidate queue review handoff 摘要,要求 inventory linkage、handoff identity、target table、row count、artifact paths、review contract、forbidden API actions 與 operator confirmation 對齊;仍不讀 token、不執行 CLI、不開 DB、不寫 queue、不更新 review_state、不掛 scheduler,只放行到人工 candidate queue review。
|
||||
- 2026-05-31 起,`V10.504` 新增市場情報 MCP Fetch Candidate Queue Writer Review Inventory gate:在 writer review handoff 通過後只審核 operator read-only candidate queue inventory 摘要,要求 handoff identity、target table、row count、dedupe keys、review_state、artifact paths、read-only query result、missing/duplicate rows 與 operator confirmation 對齊;仍不讀 token、不執行 CLI、不開 DB、不寫 queue、不更新 review_state、不做 inventory query、不掛 scheduler,只放行到後續人工 candidate queue review。
|
||||
- 2026-05-31 起,`V10.505` 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision gate:在 review inventory 通過後只審核 operator candidate queue review decision 摘要,要求 decision identity、target table、row count、dedupe keys、`needs_review` 現態、允許決策、evidence refs、matched row exact-identity/variant/overwrite guard 與 operator confirmation 對齊;仍不讀 token、不執行 CLI、不開 DB、不寫 decision record、不更新 review_state、不寫 match result、不補 queue、不掛 scheduler,只放行到 decision approval / writer preflight 設計。
|
||||
- 2026-05-31 起,`V10.506` 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval gate:在 review decision 通過後只審核 operator human approval 摘要,要求 decision linkage、approval identity、target table、row count、dedupe keys、`approved_for_writer_preflight` approval result、decision/approval evidence refs、artifact paths、matched row exact-identity/variant/overwrite guard 與 operator confirmation 對齊;仍不讀 token、不執行 CLI、不開 DB、不寫 approval record、不寫 decision record、不更新 review_state、不寫 match result、不補 queue、不掛 scheduler,只放行到後續 writer preflight 設計。
|
||||
- 2026-05-31 起,`V10.509` 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval Writer Preflight gate:在 human approval 通過後只審核 operator writer preflight 摘要,要求 approval linkage、writer_preflight_id、target operation、row count、dedupe keys、approved decision 到 target review_state 的逐列映射、decision/approval/preflight evidence refs、matched row exact-identity/variant/overwrite guard 與 operator boundary;仍不讀 token、不執行 CLI、不開 DB、不寫 preflight/approval/decision/match、不更新 review_state、不補 queue、不掛 scheduler,只放行到後續 CLI review / run package 設計。
|
||||
- 2026-06-01 起,`V10.566` 新增市場情報 Professional Source Governance gate:將 robots/REP、sitemap/lastmod、JSON-LD / schema.org structured data、canonical URL、rate limit、公開資料邊界、provenance、snapshot hash 與 idempotency key 納入 source contract,並接上 `/api/market_intel/mcp_professional_source_governance`、UI preview panel、deployment readiness check 與 production smoke target;仍不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不掛 scheduler。
|
||||
- 2026-06-04 起,`V10.579` 補 PChome 高信心 total-price safe family:SAB 私密防護舒緩噴霧 30ml、Herbacin 小甘菊 20ml 護手霜在同款式同規格且無 variant/commercial gap 時可進 `exact / total_price / price_alert_exact`;跨款式反測仍擋在 review,`MIN_MATCH_SCORE` 不變。同版將 Code Review GCP-B secondary timeout 收斂到 25 秒,GCP-A/GCP-B 都慢時更快回 local degraded。
|
||||
- 2026-06-04 起,`V10.580` 補 PChome 重複單品組 total-price 窄門與核心油種 veto:同品牌、同入數、同基礎規格且名稱高度對齊的重複單品組(例如 Bioneo 150ml x2、Cetaphil 150ml x2、Avene 300ml x4、Schick 2+1 入)可進 `exact / total_price / price_alert_exact`;正式部署前估算 213 筆高分 `true_low_confidence` 中僅 7 筆會被自動寫入。NEW DIRECTIONS 甜杏仁油 vs 杏桃核仁油改 hard veto,Paula's Choice 缺 30ml 規格的雙入組仍留 manual review;`MIN_MATCH_SCORE` 不變。
|
||||
- 2026-06-04 起,`V10.581` 將重複單品組安全線接進 retryable revalidation:只收 `true_low_confidence` 中舊診斷為 `match_type=exact / price_basis=manual_review`、無 commercial / variant / count / bundle 等阻擋,且命中具名安全商品線的候選;最後仍由最新版 matcher 與 overwrite protection 決定是否寫正式比價。
|
||||
- 2026-06-04 起,`V10.582` 補 PChome 比價通知專業分級與 Nick 副標身份證據:NemoTron 決策信封保留 MOMO / PChome 價格、價差與 7 日業績變化;Telegram decision envelope 將 `exact / total_price / price_alert_exact` 等工程路徑翻成直接價格威脅、單位價覆核、身份覆核或壓制告警,並把「單位價/身份未確認不得用總價直接告警」寫進操作邊界。PChome `Nick` 副標會以 `match_name` 參與 matcher,比價可用到容量、入數、濃度資訊,但不改 UI/DB 正式顯示品名。
|
||||
- 2026-06-04 起,`V10.583` 補 Paula's Choice 身體乳 PChome Nick 具名 alignment:`2%水楊酸身體乳210ml二入` 可和 PChome `Nick` 補出的 `水楊酸身體乳雙入組 / 210ml x2` 對齊並進 safe total-price;此版不泛用放寬中文入數,`118ml二入組(金蓋限定版)` 對上 PChome 效期品仍維持 manual review。
|
||||
- 2026-06-04 起,`V10.584` 補 PChome Nick 清洗與 stale recovery 單品窄門:Nick 先去 HTML、行銷星號與重複品名,避免同一商品副標讓規格被重複計數;新增 NIVEA 妮維雅霜 100ml、Schick 舒綺敏感肌除毛刀片 3 入、TS6 沁涼潔淨慕斯 100g 具名 exact total-price alignment。IBL 沐浴/洗髮用途落差、唇色目錄款、效期/限定版差異仍留 review。
|
||||
- 2026-06-05 起,`V10.601` 收斂 Gemini / 111 治理:正式 `ai_calls` 近 24 小時與近 7 天沒有 Gemini provider;舊 K8s/n8n/scripts/docs/Google Drive token 檔中的已知實密鑰改占位符並補全 repo secret 掃描測試;OpenClaw 日/週/月/Meta 長報告改為 GCP-A/GCP-B only,不再讓 `openclaw_meta` 落到 111。
|
||||
- 2026-06-05 起,`V10.600` 收斂 AI Intelligence 競品表前台標籤:PChome 競品 footer 不再顯示 TTL / 比對門檻等工程參數,改顯示已通過身份比對的使用者語意;已知 matcher tag 轉成中文 badge,未知 tag 隱藏,避免 raw internal tag 出現在營運畫面。
|
||||
- 2026-06-05 起,`V10.599` 補全站巡檢降載與前端工作溝通隔離:CONSTITUTION 新增第 14.2 條,禁止把施工紀錄、版本發布說明、Codex/Claude 評估、推版語氣放進使用者可見頁面;市場情報停用頁改為輕量產品狀態頁;ICAIM dashboard API 增加短快取、stale fallback、5 秒 PostgreSQL statement timeout、LATERAL 最新價與最新 PChome identity row 查詢,避免全站巡檢與使用者開頁時被重查詢拖慢。
|
||||
- 2026-06-04 起,`V10.578` 修正 Code Review deterministic scan 的 timeout 判定,多行 `requests.*(... timeout=...)` 不再被誤報為未設定 timeout。
|
||||
- 2026-06-04 起,`V10.577` Code Review OpenClaw 會在 explicit Ollama host generate 前先做短 `/api/version` preflight;GCP-A 不通時快速跳 GCP-B,避免 15 秒 timeout 後才降級,且仍不呼叫 Gemini / 111。
|
||||
- 2026-06-04 起,`V10.576` 修正 GCP-only Ollama retry:caller 禁用 111 fallback 時,resolver 若回到 111 會改試 GCP-A/GCP-B allowlist,不再讓 Hermes / Code Review 類任務因 resolver 快取到 111 而 `all 0 hosts failed`。
|
||||
- 2026-06-04 起,`V10.575` 拆分 PChome 型錄可比覆核 lane:`catalog_comparable` 會依 diagnostic evidence 分成選項/色號、單位/入數與身份採用三條人工處理路徑,Dashboard、decision envelope、coverage 與 Webcrumbs host data 使用同一套統計與 HITL guardrail。
|
||||
- 2026-06-03 起,`V10.574` 新增市場情報 Source Governance → Fetch Target bridge:`/api/market_intel/mcp_fetch_target_source_governance_review` 交叉審核 Professional Source Governance 與 MCP Fetch Target Review,要求 target `platform_code/source_key` 全部命中已治理 source contract;仍不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不執行 CLI、不掛 scheduler,只放行到後續人工 fetch run package review。
|
||||
- 2026-06-02 起,`V10.567` 將 MCP 市場洞察 fallback 收斂為 GCP-A / GCP-B only,不再讓 111 承接非即時市場分析長任務;預設 timeout 25 秒、`num_predict` 500,GCP 不可用時直接保守降級,避免 Elephant Alpha 60 秒 timeout 與 111 負載尖峰。
|
||||
- 2026-06-02 起,`V10.568` 將價格類 `decision_envelope` 的 Telegram 直送訊息改為專業 brief:標的、價格證據、比對證據、人工下一步四段式;review queue 信封 subject 同步帶 `momo_price` / `competitor_price`,讓 Telegram、PPT、Webcrumbs 與 AI 摘要共用價格證據。
|
||||
- 2026-06-02 起,`V10.569` 將 Webcrumbs host data 串到 `summarize_review_decision_envelopes()`,payload 新增 `reviewDecisionBrief` 與 review queue / HITL / auto-execute-blocked metadata;共用 UI runtime 讀同一份 PChome 覆核信封摘要,仍只讀 DB、不呼叫 LLM、不抓外站、不寫資料。
|
||||
- 2026-06-15 起,`V10.605` 修正 PChome 後台業績 Excel 匯入韌性:auto import 會掃所有 worksheet / 表頭列並選擇 `即時業績明細` 類明細 sheet,欄位或日期不合格的檔案會移至 Drive `匯入失敗` 避免 30 分鐘重複告警;同版修復 scheduler 容器缺 `pg_dump` 的備份告警。Production 匯入任務 #54 成功寫入 12,460 筆,`daily_sales_snapshot` 與 `realtime_sales_monthly` 最新日期皆為 2026-06-14;資料新鮮度 probe 降為 `gap=1 / info / notified=false`,daily/growth chart runtime guard 通過。
|
||||
- 2026-06-15 起,`V10.606` 正名為「PChome 業績成長自動化作戰系統」並新增只讀 `/api/ai/pchome-growth/opportunities`:作戰清單以 PChome 後台業績為主、MOMO 作為外部價格參考,蝦皮與酷澎先暫停且不進告警。正式只讀盤點確認 `daily_sales_snapshot."商品ID"` 與 `competitor_prices.competitor_product_id` 直接重疊為 0,因此第一版不硬接 ID;無可驗證對應時只輸出「先補商品對應」任務。AI 競情頁同步改成白話營運文案,避免把工程術語直接呈現給使用者。
|
||||
|
||||
## 3. 12 Agent 決策信封整合
|
||||
|
||||
- `decision_envelope` 已接到 NemoTron 價格告警與人工覆核,下一步要讓 OpenClaw、ElephantAlpha、PPT QA 與 review queue 共用同一份 evidence contract。
|
||||
- 2026-05-24 22:44 CST 起,EventRouter 對已附 `decision_envelope` 的事件直接渲染證據模板,不呼叫 L1/L2 AI handler;這讓 NemoTron 價格告警、人工覆核與後續 Agent 共用同一份 SKU / PChome / evidence / guardrails,不再二次生成摘要。
|
||||
- 2026-05-24 23:00 CST 起,`fetch_competitor_review_queue()`、`fetch_competitor_review_queue_page()` 與 `/api/pchome-review/queue` 每筆候選也帶 `decision_envelope`,包含 SKU/PChome 標的、match evidence、人工下一步、預期價差與不可自動寫正式價差的 guardrails;Dashboard、Agent、Telegram、PPT 後續共用此 contract。
|
||||
- 2026-05-24 23:15 CST 起,Dashboard 覆核卡與 PChome 覆核 Excel 匯出也顯示/輸出信封摘要、資料品質、HITL、trace、自動執行阻擋原因與證據摘要;下載檔不得丟失 guardrails。
|
||||
- 2026-05-24 23:25 CST 起,OpenClaw 週報/日報/月報與 competitor PPT 使用 `summarize_review_decision_envelopes()` 的同一份 HITL 信封摘要,不再手寫 attempt_status 統計或自行翻譯覆核狀態。
|
||||
- 2026-05-24 23:40 CST 起,`compare_existing_identity` 成為 `protected_existing_match` 的明確建議動作;Agent 只能提示「比較既有正式候選與新候選」,不得因新候選分數較高自動寫正式價差。
|
||||
- 2026-05-24 23:55 CST 起,ElephantAlpha `resource_optimization` / `ea_escalation` 都必須帶 deterministic `decision_envelope`;Telegram 按鈕 callback 使用 `decision_id`,證據只允許 action_plans、CPU 實測、hygiene 與 trigger trace。
|
||||
- 告警不得再輸出空泛「預期效益」;必須帶資料品質、證據來源、HITL 邊界與 trace id。
|
||||
- Agent 建議只能輔助排序與分析,不得繞過 matcher / feeder / review service 寫正式價格。
|
||||
|
||||
## 3.1 Ollama / Embedding 健康
|
||||
|
||||
- 2026-05-25 08:48 CST 起,`OllamaService.generate_embedding(..., allow_111_fallback=False)` 若 resolver 回 111,會強制改試尚未嘗試的 GCP-A/GCP-B,不再讓背景 embedding 在 111 disabled 情境直接退出或只試單台 GCP-B;111 仍不承接背景 `bge-m3`。
|
||||
- 2026-05-25 12:27 CST 起,背景 embedding 在 GCP-A/GCP-B 全掛時開啟短暫 failure circuit;這是降載保護,不代表 primary 已恢復。部署 smoke 時 GCP-B `/api/version` 已恢復 200,下一步仍需恢復 GCP-A Ollama 或更新 110 的可用 SSH/GCP 操作憑證。
|
||||
- 2026-05-25 12:37 CST 起,背景 embedding GCP-only failure circuit 改用 WARNING 記錄,避免可預期降級污染 ERROR 告警通道。
|
||||
- 2026-05-25 13:35 CST 起,GCP-B `bge-m3` 實測 P95 波動已超過 15s,背景 embedding / host health model probe timeout 預設改 30s;若 30s 仍常 timeout,需進一步處理 GCP-B runner/CPU/模型併發,而不是再把 111 納入背景任務。
|
||||
- 2026-05-25 14:10 CST 起,GCP-A refused 已明確歸類為 infra blocker;應用層不得改成 111 背景 fallback,也不得把 110:11435 502 當成 momo-app 故障。背景 embedding 可依 `host_health_probes` 跳過近期 unhealthy GCP host,但查 DB 必須 fail-open。
|
||||
|
||||
## 4. 業績分析資料與圖表修復
|
||||
|
||||
- 修正即時業績匯入 `snapshot_date text = date` 類型錯誤。
|
||||
- PChome 後台業績匯出前半段仍需自動化:優先確認 PChome 後台是否支援排程寄信 / Email 附件 / FTP / API;若無,改做 110 或獨立 worker 的受控 browser 下載,再把檔案放進 Google Drive `當日業績匯入/`。
|
||||
- `/daily_sales`、`/growth_analysis` 圖表不得空白;需保留原本圖表並升級成更專業的呈現。
|
||||
- 圖表需通過 runtime nonblank canvas 檢查與手機版 responsive。
|
||||
- daily/growth/PPT 必須共用 `competitor_intel_repository` 的比價資料出口,避免價差方向或統計口徑分裂。
|
||||
- 2026-05-24 23:55 CST 起,daily/growth chart 判斷不再只看 series 長度;若序列全 0,顯示 chart-empty 狀態而不是畫只有座標軸的假圖。正式 smoke 需跑 `scripts/check_sales_charts_runtime.js` 確認主要 canvas 非空。
|
||||
|
||||
## 5. PPT 視覺 QA 與自動簡報產線
|
||||
|
||||
- 每日、每週、每月、每季、半年、年度簡報需依排程產出。
|
||||
- 每次產出與視覺 QA 結果必須完整寫入 DB。
|
||||
- `/observability/ppt_audit_history` 必須清楚顯示 runtime 狀態、產出狀態、視覺 QA、問題追蹤與可預覽檔案。
|
||||
- PPTX / PDF 預覽需可站內直接開啟,不能只下載。
|
||||
- 2026-06-06 起,`V10.604` 修正定期簡報長期漏產:根因為 `schedule.run_pending()` 同步執行,20:30/20:40/20:50 精準時段會被 feeder / AI 長任務卡過且不自動 replay。新增每 10 分鐘 missed-run catch-up、scheduler 背景化補跑、排程型市場情報與價格甜蜜點快速 fallback;production 已補齊 `daily`、`market_intel`、`price_elasticity`,catch-up plan 顯示 daily/weekly/monthly/quarterly/half_yearly/annual 全數 ready,`/observability/ppt_audit_history` 可看到新檔與預覽入口。
|
||||
|
||||
## 6. 外部 BI / 協作入口
|
||||
|
||||
- `/metabase` 不可空白,需顯示可診斷 bridge 狀態或可用替代入口。
|
||||
- `/grist` / 資料協作連結不得連到其他專案站。
|
||||
- 側欄與 topbar 外部工具入口要統一走 momo-pro bridge route。
|
||||
|
||||
## 7. AI 觀測台與全站 UI/UX
|
||||
|
||||
- 10 個 AI 觀測台頁面必須符合新版字體、字級、暖墨色、焦糖 accent、點陣視覺與 responsive 規範。
|
||||
- 全站主要頁面需通過 desktop / mobile overflow guard。
|
||||
- 表格與圖表只允許在局部容器橫向滾動,不可造成整頁無限延伸。
|
||||
|
||||
## 8. 效能與可觀測性
|
||||
|
||||
- 持續降低 `/daily_sales`、`/growth_analysis`、商品看板、PChome queue、PPT audit 首屏 TTFB。
|
||||
- 避免 worker cold start 重查重算;必要時使用共享快取與指紋失效。
|
||||
- 111 fallback 只作最後救急;持續監控 GCP-A / GCP-B / 111 用量與 circuit breaker。
|
||||
|
||||
## 9. 每輪收尾
|
||||
|
||||
- Focused tests → full pytest 或合理範圍回歸 → production smoke。
|
||||
- 更新 SOT / memory / TODO。
|
||||
- 推 Gitea,正式部署,確認 `/health` 版本。
|
||||
- 記錄未完成與下一輪入口。
|
||||
|
||||
## 10. 2026-06-15 V10.607 外部市場來源正規化
|
||||
|
||||
- 新增 `external_market_sources` / `external_offers`,作為 MOMO、未來蝦皮、未來酷澎、供應商 API 與手動 CSV 的共同資料入口。
|
||||
- `/api/ai/pchome-growth/source-contract` 提供只讀來源狀態與欄位 contract;UI 只顯示白話狀態,例如「正在使用」「先暫停」「可用資料」。
|
||||
- MOMO 目前先橋接既有已確認同款的比價快取;蝦皮與酷澎只保留 contract,預設暫停、不進告警。
|
||||
- 下一步:做手動 CSV 匯入 dry-run 與外部報價品質檢查頁,讓未來無論官方 API 或 provider API 都能先經過同一套品質門檻。
|
||||
|
||||
## 11. 2026-06-15 V10.608 外部報價 CSV 預檢
|
||||
|
||||
- 新增 `/api/ai/pchome-growth/external-offers/csv-dry-run`,接受 CSV 檔案或貼上的 CSV 文字,只做預檢、不寫 DB。
|
||||
- AI 情報頁新增「外部報價預檢」區塊,顯示可使用、待確認、不能使用;用字保持白話,不顯示工程欄位給一般使用者。
|
||||
- 預檢支援中文表頭,例如「資料來源、外部商品ID、商品名稱、售價、資料時間、取得方式、PChome商品ID、同款狀態、資料可信度」。
|
||||
- CSV 預檢是備援入口,不是日常主流程。
|
||||
|
||||
## 12. 2026-06-15 V10.609 外部報價自動同步
|
||||
|
||||
- 新增 `sync_legacy_momo_reference_offers()`,自動把已確認同款的既有比價快取同步進 `external_offers`。
|
||||
- 新增 `run_external_offer_sync_task`,每 4 小時自動執行;排在 competitor feeder 後,同步 MOMO 外部價格參考資料層。
|
||||
- CSV 保留為 API / crawler / provider 故障時的救援預檢;日常目標是自動抓、自動同步、自動進作戰清單。
|
||||
|
||||
## 13. 2026-06-16 V10.610 PChome 作戰清單優先讀新資料層
|
||||
|
||||
- `/api/ai/pchome-growth/opportunities` 已改成優先讀 `external_offers`,只有缺資料時才 fallback 舊 `competitor_prices`。
|
||||
- `external_offers.raw_payload_json` 會保留舊比價快取中的 PChome 公開價,讓新資料層仍可算出 MOMO / PChome 價差。
|
||||
- API stats 新增 `external_data_source_counts`,可看到「自動同步資料層」與「舊比價快取」各有多少筆。
|
||||
- 下一步:把其他比價報表與 AI 告警逐步改讀 `external_offers`,讓 `competitor_prices` 降為 bridge/cache。
|
||||
|
||||
## 14. 2026-06-16 V10.611 作戰入口與資料來源可見化
|
||||
|
||||
- `/ai_intelligence` 是 PChome 業績成長自動化作戰系統的營運主入口;V10.617 起舊「今日作戰入口」已改為「今日重點總覽」,首屏需先顯示下一步與今日處理清單,不再把備援 CSV 放在主要流程前段。
|
||||
- PChome 成長作戰區塊會依 API stats 顯示今日優先動作,例如先補商品對應、先處理可直接比價商品,避免使用者只看到數字不知道下一步。
|
||||
- 同區塊新增資料來源摘要,直接顯示「自動同步資料層」或「舊比價快取」各有多少筆,方便確認 V10.610 新資料層是否真的被作戰清單採用。
|
||||
- CSV 預檢在 UI 文案上維持備援定位;日常主流程仍是自動同步外部報價。
|
||||
|
||||
## 15. 2026-06-16 V10.612 MOMO 外部價格參考優先讀新資料層
|
||||
|
||||
- `/api/ai/icaim/dashboard` 的「MOMO 外部價格參考」已改為優先使用 `external_offers`,同一 MOMO SKU 若有自動同步資料就不再先讀舊 `competitor_prices`。
|
||||
- 表格 stats 新增 `competitor_data_source_counts`,前端 footer 顯示「自動同步資料層 / 舊比價快取」各幾筆;每列狀態會顯示「自動同步」或「舊資料」。
|
||||
- 價差與高風險統計改採 PChome 視角:正數代表 PChome 比 MOMO 外部參考價高,才列入需檢查價格。
|
||||
- 下一步:把 Hermes / ElephantAlpha / AI product pick 等後端價格分析逐步改讀 `external_offers`,讓告警與頁面使用同一份資料來源。
|
||||
|
||||
## 16. 2026-06-16 V10.613 高可見頁面繁中化守門
|
||||
|
||||
- 使用者要求所有內容與頁面必須使用繁體中文;工程內部變數、CSS class、API key 名稱可保留英文,但使用者可見標題、按鈕、狀態與說明不得用英文工程語。
|
||||
- 已把 `/code-review/` 顯示文字改成「AI 程式碼審查」「流程進度」「程式碼審查完成」,並將 OpenClaw 模型顯示改成「Ollama 優先」,避免前台誤以為 Gemini 是主路徑。
|
||||
- 已把 AI 自動化健康檢查頁改為白話繁中命名,狀態顯示改成「正常 / 注意 / 嚴重 / 產生時間」。
|
||||
- 已把 PPT 觀測台與商品看板高可見英文標籤改成「產線健康度 / 工作隊列 / 視覺問題 / 產線控制台 / 覆蓋率流程」,並新增測試防回歸。
|
||||
|
||||
## 17. 2026-06-16 V10.614 部署與基礎設施頁繁中化
|
||||
|
||||
- `/cicd` 對使用者顯示為「部署監控」,不再以前台標題顯示 `CI/CD Dashboard`;部署流程、部署歷史、GitLab 部署紀錄皆使用白話繁中。
|
||||
- 部署流程圖會把後端階段代碼 `test / build / deploy` 轉成「測試 / 建置 / 部署」,診斷狀態也轉成「正常 / 注意 / 失敗」。
|
||||
- `/observability/host_health` 與 PPT 產線視覺狀態把 `Runtime` / `Vision QA` 改成「執行環境」與「視覺檢查」,並更新測試防回歸。
|
||||
|
||||
## 18. 2026-06-16 V10.615 AI 智慧推薦頁 Ollama 主路徑文案
|
||||
|
||||
- `/ai_recommend` 的 AI 路徑顯示改成「Ollama 主路徑 / Gemini 備援」,Gemini 選項保留為角色提示但停用手動選擇,避免使用者誤以為可直接用 Gemini 生成文案。
|
||||
- `page-ai-recommend.js` 的狀態 badge、生成結果 meta、搜尋/分析錯誤訊息改用繁中全形冒號與「權杖」用語。
|
||||
- 新增測試守門:禁止 `Ollama (本地)`、`Gemini (雲端)`、`Web Search`、`Token:` 與半形英文錯誤前綴回到 AI 智慧推薦頁。
|
||||
|
||||
## 19. 2026-06-16 V10.616 主商品看板統計標籤繁中化
|
||||
|
||||
- `/` 主商品看板補齊高可見統計標籤繁中化,將 `ACTIVE`、`PICK COUNT`、`AVG CONFIDENCE`、`EVIDENCE GAP` 等工程詞改為「有效商品」「挑品數」「平均信心」「待補證據」。
|
||||
- PChome 補強區塊標籤改為「PChome 比價補強」,空狀態改為「目前有效商品沒有高優先 PChome 覆核項目」。
|
||||
- 測試新增禁止英文工程標籤回歸,讓主商品看板符合「所有內容包含頁面皆為繁體中文」紅線。
|
||||
|
||||
## 20. 2026-06-16 V10.617 AI 情報頁改為作戰導向 UI
|
||||
|
||||
- `/ai_intelligence` 不再只堆說明文字;首屏新增「下一步」動態指令、商品處理進度、外部價格來源與操作捷徑。
|
||||
- 今日處理清單改為表格,欄位優先呈現「優先級、建議動作、商品、近 7 天業績、比價結果、資料可信度、下一步」,讓使用者先看到該做什麼,以及資料能不能信。
|
||||
- MOMO 外部價格參考新增價格風險分佈,表格改為 PChome 價格在前、MOMO 參考價在後,價差明確顯示「PChome 貴 / PChome 便宜」,並新增「鎖定商品」操作。
|
||||
- 備援 CSV 流程降級為「備援資料檢查」,移到主要作戰與價格表後面,避免誤導使用者以為日常仍要人工匯入。
|
||||
- 前端補上 payload fallback、動態表格 escape、手機版 `data-label` 與補商品對應 busy lock,避免資料缺欄位、特殊字元或重複點擊造成壞畫面。
|
||||
|
||||
## 21. 2026-06-16 V10.618 比價頁改為下一步導向
|
||||
|
||||
- `/price_comparison` 改為「PChome 商品比價決策台」,首屏需先顯示「今天先做」與 PChome / MOMO 商品準備狀態,不再讓使用者從 Step 1/2/3 自行猜流程。
|
||||
- 頁面會依目前資料狀態切換下一步:輸入關鍵字、取得 PChome 商品、匯入 MOMO 商品、開始檢查價差、查看需檢查價格或可主推商品。
|
||||
- 比價結果新增判讀分佈:「需檢查價格」「可主推曝光」「價格接近」,表格第一欄直接呈現每筆商品下一步。
|
||||
- Toast 改用純文字 DOM,手動輸入錯誤訊息不再塞 HTML;更新商品資料時會清掉舊比價結果,避免資料已更新但畫面仍顯示舊判讀。
|
||||
|
||||
## 22. 2026-06-16 V10.619 PChome 導向 MOMO 精準候選搜尋
|
||||
|
||||
- 使用者指出只抓 MOMO 活動頁會讓比價候選池偏窄;V10.619 新增 `search_momo_products_for_pchome_products()`,用 PChome 商品名稱逐筆反查 MOMO 候選。
|
||||
- 搜尋詞沿用 `marketplace_product_matcher.build_search_terms()`,保留品牌、品名、容量、單品與組合線索,例如 B5 40ml、500ml 2入組,避免只用品牌或活動頁商品池。
|
||||
- `/api/price_comparison/compare` 在已有 PChome 商品但缺 MOMO 清單時,會優先走 PChome 導向 MOMO 搜尋;完全沒有 PChome 商品時才退回品牌搜尋。
|
||||
- MOMO 搜尋 parser 已補新版 Next.js `goodsInfoList`,避免明明搜尋頁有商品但 crawler 回 0 筆。
|
||||
- `/price_comparison` 已新增「自動找 MOMO 候選」操作,PChome 商品準備後可直接搜尋 MOMO;回傳會分成「可直接比價」與「需人工確認」。
|
||||
- 新路徑只擴大候選池,不放寬 `score_marketplace_match()` 的 hard veto 與同款分數篩選;V10.619 當時先把 `unit_comparable` 候選保留為「需人工確認」,此限制已由 V10.620 的自動單位價分流取代。
|
||||
|
||||
## 23. 2026-06-16 V10.620 單位價候選自動化分流
|
||||
|
||||
- 使用者要求把需要人工處理的比價工作降到最低;V10.620 將 PChome 導向 MOMO 搜尋的 `unit_comparable` 候選改成三路分流:同款總價、可自動換算單位價、真正需人工確認。
|
||||
- `search_momo_products_for_pchome_products()` 會在 `unit_comparable` 時呼叫 `build_unit_price_comparison()`;只有能算出雙方總容量/數量、單位價與差距百分比時,才標成 `auto_compare_type=unit_price` 與「自動單位價比較」。
|
||||
- `/api/price_comparison/fetch_momo_for_pchome` 回傳 `products`、`unit_compare_candidates`、`review_candidates` 三段;舊總價比價只吃 `products`,避免把組合包總價誤當同款價差。
|
||||
- `/price_comparison` 顯示「同款 / 單位價 / 需確認」三個數量,並新增自動單位價面板;若只找到單位價候選,下一步會引導使用者查看單位價結果,而不是人工確認。
|
||||
|
||||
## 24. 2026-06-16 V10.621 自動候選接入外部價格參考
|
||||
|
||||
- `/price_comparison` 正常操作「自動找 MOMO 候選」時會帶 `sync_external_offers=true`,把可直接總價比價與自動單位價候選同步進 `external_offers`;仍需人工確認的候選不寫入。
|
||||
- 新增 `sync_targeted_momo_candidates_to_external_offers()`,只寫 `ingestion_method='targeted_momo_search'`、`match_status='verified'`、`data_quality_status='verified'` 的安全候選;`unit_price` 候選會在 `raw_payload_json.unit_price_comparison` 保留 MOMO / PChome 單位價、容量/數量與價差百分比。
|
||||
- `build_pchome_growth_opportunities()` 已能讀 `external_offers.raw_payload_json.price_basis='unit_price'`:作戰清單會顯示「資料可用單位價判斷」,並用單位價差距做「檢查售價與活動 / 放大價格優勢」判斷。
|
||||
- 此路徑只同步外部價格參考與作戰清單,不寫 `competitor_prices`,不自動改價;目標是減少人工補資料,而不是放寬正式價差寫入。
|
||||
|
||||
## 25. 2026-06-16 V10.622 外部報價同步後即時刷新作戰清單
|
||||
|
||||
- 新增 `services/pchome_growth_cache_state.py`,以 `data/pchome_growth_cache_epoch.txt` 作為跨 Gunicorn worker 的作戰清單快取失效標記。
|
||||
- `sync_legacy_momo_reference_offers()` 與 `sync_targeted_momo_candidates_to_external_offers()` 只要成功寫入 `external_offers`,就呼叫 `mark_pchome_growth_cache_stale()`。
|
||||
- `/api/ai/pchome-growth/opportunities` 的 in-memory cache 會記住建立時的 epoch;讀快取前若發現共享 epoch 較新,會直接重建,不再讓使用者看到 120 秒舊清單。
|
||||
- 這讓「自動找 MOMO 候選 → 同步外部價格參考 → AI 情報頁作戰清單」變成同一條即時資料流,減少使用者手動重新整理或等待快取過期。
|
||||
|
||||
## 26. 2026-06-16 V10.623 比價與作戰頁工作台化
|
||||
|
||||
- 使用者指出前端仍像文字堆疊,無法快速知道怎麼操作;V10.623 將 `/price_comparison` 第一屏改為主 KPI、目前卡點、四步流程與下一步 CTA。
|
||||
- `/price_comparison` 的結果區新增決策摘要,先顯示「需檢查售價或活動 / 可主推曝光 / 觀察賣點」三類數字與建議,再往下看明細表。
|
||||
- `/ai_intelligence` 第一屏新增今日任務摘要,直接顯示今日任務、可立即處理、待補比價與最新業績日;資料來自現有 PChome growth API stats。
|
||||
- 測試守門新增 `priceDecisionGrid`、`price-workflow-strip`、`price-result-summary-grid`、`growth-executive-strip` 與 `renderGrowthExecutiveSummary`,避免頁面退回只有文字說明的狀態。
|
||||
|
||||
## 27. 2026-06-18 V10.624 ElephantAlpha 價格決策只進 HITL
|
||||
|
||||
- 部署後觀察到 `price_drop_alert` 進入 ElephantAlpha execution plan 後可能卡在 Hermes/NemoTron step,最後以 60 秒 timeout 污染 scheduler log。
|
||||
- V10.624 將價格類 trigger 的高信心路徑改為「有實證就發 L3 HITL 價格覆核通知」,不再執行 orchestrator `execution_plan`,避免長任務 timeout 與自動調價誤解。
|
||||
- 新增 `price_decision_review` 決策信封,固定標示 `can_auto_execute=false`、`requires_hitl=true`、`execution_plan skipped`;通知只呈現 DB/Hermes 具體價差實證。
|
||||
- 測試新增高信心價格決策不執行長任務 step 的守門,避免未來又把價格告警回退成自主執行。
|
||||
|
||||
## 28. 2026-06-18 V10.625 背景 embedding 熔斷不扣 retry
|
||||
|
||||
- 部署 V10.624 後 scheduler log 顯示 GCP-A 最近 host health 不健康,GCP-B `bge-m3` 仍可能 30 秒 timeout;embedding worker 會在同批任務中連續把多筆 queue 標成失敗,造成 attempts 被白白消耗。
|
||||
- V10.625 將 GCP embedding failure circuit 狀態公開為 `is_embedding_gcp_circuit_open()` / `embedding_gcp_circuit_remaining_seconds()`,讓 worker 可用明確狀態判斷,不再猜測空向量原因。
|
||||
- `OpenClawLearningService` worker 在熔斷中不 claim 新任務;若處理中開啟熔斷,當筆與同批剩餘任務會退回 `pending` 並寫入延後原因,不扣 `attempts`、不刷成 `failed`。
|
||||
- 背景 embedding 仍維持 GCP-A → GCP-B,不落 111;111 不承接 `bge-m3` 背景批次的治理規則不變。
|
||||
|
||||
## 29. 2026-06-18 V10.626 GCP-A direct timeout 改走 110 proxy rescue
|
||||
|
||||
- 正式診斷腳本顯示:188 直連 GCP-A `34.87.90.216:11434` `/api/version` timeout,但 GCP-B direct、111、110 `11435` primary proxy、110 `11436` secondary proxy 都可用;GCP-B `bge-m3` embed 實測約 2.9 秒。
|
||||
- V10.626 新增 `OLLAMA_HOST_PRIMARY_PROXY` / `OLLAMA_HOST_SECONDARY_PROXY`,預設為 `http://192.168.0.110:11435` / `http://192.168.0.110:11436`。
|
||||
- `resolve_ollama_host()` 順序調整為 GCP-A direct → GCP-A via 110 proxy → GCP-B direct → GCP-B via 110 proxy → 111;proxy rescue 是同順位入口救援,不代表 direct GCP host 已恢復。
|
||||
- 近 24 小時 `ai_calls` 只有 `ollama_secondary=51`、`gcp_ollama=3`、`nim=1`,沒有 Gemini provider;Gemini hard disabled / fallback disabled 的紅線仍有效。
|
||||
|
||||
## 30. 2026-06-18 V10.627 Resolver 讀 host_health 跳過 direct timeout
|
||||
|
||||
- V10.626 已能在 GCP-A direct timeout 後走 110 proxy,但 cache refresh 仍會先等一次 direct `/api/version` timeout。
|
||||
- V10.627 新增 direct-only host health skip:`resolve_ollama_host()` 會讀最近 `host_health_probes`,若 GCP-A/GCP-B direct 在視窗內已 unhealthy,先跳過 direct endpoint,改試同順位 110 proxy;proxy rescue 不吃這個 skip,避免因 direct unhealthy 誤跳過可用 proxy。
|
||||
- 新增 `OLLAMA_RESOLVE_HOST_HEALTH_SKIP_ENABLED=true` 與 `OLLAMA_RESOLVE_HOST_HEALTH_SKIP_WINDOW_MINUTES=20`;DB 讀取失敗 fail-open,回到原本網路探測。
|
||||
|
||||
## 31. 2026-06-18 V10.628 備份 partial 檔案清理
|
||||
|
||||
- 正式 `backup_log` 最新狀態已是 2026-06-18 02:00 成功備份,6/15 的 `pg_dump` not found 是舊失敗紀錄;`get_latest_backup_info()` 目前回 success,backup monitor 不會再因舊 row 告警。
|
||||
- 備份目錄仍殘留 0 byte `momo_analytics_*.sql.gz` partial 檔,容易讓人工查檔誤判。
|
||||
- V10.628 新增 `cleanup_partial_backups()`,`cleanup_old_backups()` 會先清除超過 `PARTIAL_BACKUP_MIN_AGE_MINUTES=60` 的 0 byte partial 備份;剛產生的 0 byte 檔不刪,避免誤傷正在寫入的備份。
|
||||
@@ -12,6 +12,307 @@
|
||||
|
||||
## 📅 詳細更新日誌 (考古存檔)
|
||||
|
||||
### 2026-06-01:PChome 比價新鮮度操作閉環
|
||||
- **V10.601 Gemini / 111 治理收斂與全 repo 已知密鑰清除**: 正式 `ai_calls` 近 24 小時與近 7 天 provider 彙總確認沒有 Gemini 出站,Gemini 仍由 `GEMINI_API_HARD_DISABLED=true` 與 `GEMINI_FALLBACK_ENABLED=false` 擋住。清除舊 K8s manifest、n8n workflow、監控/auto-repair scripts、Superset 文件、Google Drive token 檔與歷史文件中的已知實密鑰,改成占位符並新增 secret 掃描測試;OpenClaw 日/週/月/Meta 等敘事長報告改為 GCP-A/GCP-B only,不再讓 `openclaw_meta` fallback 到 111 承接長文生成。
|
||||
- **V10.600 AI Intelligence 競品表前台標籤收斂**: PChome 競品表 footer 改為使用者可理解的身份比對說明,不再顯示 TTL 與全域門檻等工程參數。前端 tag renderer 只把 `identity_v2`、`match_type_exact`、`price_alert_exact`、`evidence_*`、`match_*` 等已知 matcher 診斷轉成中文 badge,未知內部 tag 不顯示,避免把 raw matcher code 暴露到營運畫面。
|
||||
- **V10.599 全站巡檢降載與前端工作溝通隔離**: 新增 CONSTITUTION 第 14.2 條,禁止把施工紀錄、版本發布說明、AI 工作視窗判斷、Codex/Claude 評估或 Gitea 推版語氣放進使用者可見前端頁面。市場情報停用頁改成輕量產品狀態頁,移除 `system_version` 與工程文案;ICAIM 競情 dashboard API 新增 120 秒快取、900 秒 stale fallback、PostgreSQL 5 秒 statement timeout、LATERAL 最新價查詢與 DISTINCT ON 最新 PChome identity row,避免全站巡檢或使用者開頁時被重型查詢拖慢。
|
||||
- **V10.584 PChome Nick 去重 + stale recovery 單品窄門**: PChome `Nick` 進 matcher 前會去除 HTML 標籤、星號行銷文與重複品名,避免同一個 `29g / 100ml` 被副標重複計數後誤判 `component_count_conflict`。依 10 筆正式 stale recovery 診斷,新增 NIVEA 妮維雅霜 100ml、Schick 舒綺敏感肌除毛刀片 3 入、TS6 沁涼潔淨慕斯 100g 的具名 exact total-price alignment;IBL 沐浴精+洗髮精 vs 洗髮精、唇釉色號目錄款、Paula's Choice 效期/金蓋差異仍維持 identity review。
|
||||
- **V10.583 Paula's Choice 身體乳 PChome Nick 具名 alignment**: matcher 新增 Paula's Choice `2%水楊酸身體乳210ml二入` 的窄範圍 alignment,讓 PChome `Nick` 補出的 `水楊酸身體乳雙入組 / 210ml x2` 可和 MOMO 品名對齊並進 `exact / total_price / price_alert_exact`。此版不泛用放寬中文入數;`118ml二入組(金蓋限定版)` 對上 PChome 效期品仍維持 `manual_review / identity_review`,不寫正式價差。
|
||||
- **V10.582 PChome 比價通知專業分級 + Nick 副標身份證據**: NemoTron 價格決策信封補齊 `momo_price`、`competitor_price`、`candidate_gap_pct` 與 `sales_7d_delta_pct`,避免 EventRouter / Telegram 模板拿不到核心價格事實。價格決策模板新增「通知分級」,將 `match_type / price_basis / alert_tier` 翻成直接價格威脅、單位價覆核、身份覆核或壓制告警,並同步顯示操作邊界;單位價或 identity 未確認時明確禁止以總價直接判定價格威脅。PChome crawler 另保留 `Nick` 副標為 `match_name` 給 matcher 使用,UI/DB 顯示仍用原品名,讓容量、入數、濃度資訊可參與比對。
|
||||
- **V10.581 重複單品組 retryable revalidation 接線**: 將 V10.580 的安全 matcher 線接到 `run_retryable_candidate_revalidation()`,但只收 `true_low_confidence` 中舊診斷已是 `match_type=exact / price_basis=manual_review`、沒有 review block,且命中具名安全商品線的候選。這讓 Bioneo / Cetaphil / Avene / Schick / KOSE 等重複單品組可以小批次重評並由最新版 matcher 決定是否寫入正式 `competitor_prices`,仍不開放泛用高分掃描。
|
||||
- **V10.580 PChome 重複單品組 total-price 窄門 + 核心油種 veto**: matcher 將同品牌、同入數、同基礎規格且名稱高度對齊的重複單品組,從 `exact / manual_review` 收斂到 `exact / total_price / price_alert_exact`;正式部署前以最新 `true_low_confidence` 重算,213 筆高分候選中僅 7 筆符合自動寫入安全條件。同步新增 NEW DIRECTIONS 甜杏仁油 vs 杏桃核仁油 hard veto,避免同容量同按壓頭但核心油種不同的候選被誤放行;PChome 端缺規格的 Paula's Choice 雙入組仍停在 manual review。
|
||||
- **V10.579 PChome 高信心 total-price safe family + Code Review timeout 收斂**: matcher 新增 SAB 私密防護舒緩噴霧 30ml 與 Herbacin 小甘菊 20ml 護手霜的窄範圍 total-price safe 路徑。這些候選仍必須通過既有 score、hard veto、variant/commercial gap 與 overwrite protection;Herbacin 柔皙 vs 野生玫瑰跨 variant 反測維持不進正式價差。目的在不放寬 `MIN_MATCH_SCORE` 的前提下,把可證明同款的高信心 `true_low_confidence` 轉進正式比價覆蓋。同版將 Code Review GCP-B secondary timeout 預設由 60 秒收斂到 25 秒,GCP-A preflight 不通且 GCP-B 生成卡住時更快回 deterministic local degraded,不呼叫 Gemini/111。
|
||||
- **V10.578 Code Review 靜態掃描 timeout 誤報修正**: Hermes deterministic scan 對 `requests.get/post/put/delete/patch` 改檢查同一呼叫 block 的後續行,已在多行呼叫中帶 `timeout=` 時不再報「HTTP request 未設定 timeout」。這修掉 V10.577 preflight helper 被 Code Review 自己誤判為 MEDIUM 的噪音。
|
||||
- **V10.577 Code Review Ollama host preflight**: OpenClaw 架構評估在 explicit GCP host generate 前先以短 `/api/version` 探測健康度;若 GCP-A 從 188 連線 timeout,會快速跳到 GCP-B `gemma3:4b`,避免每次等 primary generate timeout。此 preflight 只作用於 Code Review Ollama-first 路徑,仍維持 111 預設禁用、Gemini hard-disabled 預設不呼叫。
|
||||
- **V10.576 PChome backlog lane 與 GCP-only Ollama retry 修補**: `/api/ai/pchome-match/backfill/status` 的 coverage 與 operation backlog 同步輸出 `catalog_variant_review`、`catalog_unit_review`、`catalog_identity_review`,讓 Dashboard 操作建議可直接跳到三條人工隊列。同版修正 `OllamaService.generate(allow_111_fallback=False)`:若 resolver 已快取到 111,會改試尚未嘗試的 GCP-A/GCP-B allowlist,不再直接 `all 0 hosts failed`,同時仍避免長分析落到 111。
|
||||
- **V10.575 PChome 型錄可比覆核 lane 分流**: `catalog_comparable` 進一步拆成 `catalog_variant_review`、`catalog_unit_review` 與 `catalog_identity_review`。Coverage SQL、review queue filter、Dashboard 分段、decision envelope 與 Webcrumbs host data 共用同一套 helper,將選項/色號/款式、入數/商業條件、身份採用三種人工閉環路徑分開統計與瀏覽;仍維持 HITL,不自動寫正式價差。
|
||||
- **V10.574 PChome 型錄/任選可比覆核隊列**: 將 V10.572 的 `catalog_comparable_count` 派生口徑正式接進 PChome review queue。高分、無 hard veto、具同品線身份證據但仍有任選/型錄/商業條件待確認的 `true_low_confidence` 會進獨立 `catalog_comparable` 篩選、狀態標籤與 decision envelope;真正 `true_low_confidence` 會排除這批候選,避免重複出現在「證據不足」。此變更不放寬 `MIN_MATCH_SCORE`、不寫正式 `competitor_prices`、不算 exact matched,只把最有機會人工批次確認的候選變成可操作隊列。
|
||||
- **V10.574 市場情報 Source Governance → Fetch Target bridge**: 新增 `/api/market_intel/mcp_fetch_target_source_governance_review`、preview service 與市場情報頁 bridge panel,交叉審核 Professional Source Governance 與 MCP Fetch Target Review。此 gate 要求每個 target `platform_code/source_key` 都能對上已通過治理的公開 source contract,並同步納入 deployment readiness preview-safe check 與 production smoke target;API/UI 仍不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不執行 CLI、不掛 scheduler。
|
||||
- **V10.572 PChome 決策支援覆蓋率分層**: 覆蓋率不再只有 exact `decision_ready_rate`。`fetch_competitor_coverage()` cache 升到 v11,新增 `catalog_comparable_count`、`decision_support_count`、`decision_support_rate` 與非 exact 支援數;只納入高分、無 hard veto、同時具型錄/任選/商業條件訊號與強身份證據,且排除品類、品線、入數、香味、型號、價格極端等硬衝突的候選。Dashboard、daily、growth 與 backfill JS 同步顯示「決策支援覆蓋率 / 精準可告警覆蓋 / 型錄可比 / 單位價」,提升可用情報覆蓋但不污染正式 `matched`。
|
||||
- **V10.571 PChome pending 覆蓋率搜尋召回**: `competitor_price_feeder` 預設每個商品最多搜尋詞由 5 組提升為 6 組,並新增 `PCHOME_FEEDER_SEARCH_COVERAGE_RESCUE_ENABLED`。補抓流程會在主要 matcher 搜尋詞與原始名稱 fallback 之間加入狹義 coverage rescue terms,保留 `5.5g` / `2.4g` 等小數規格,並過濾外出清潔、卸除髒汙、卸防曬等非身份核心噪音。正式 pilot 顯示 CeraVe / TUNEMAKERS / Embryolisse / Neogence / NIVEA 這類雙語品牌商品常卡在 PChome 搜尋召回,因此補上「英文品牌 + 中文品牌 + 核心身份 + 規格」窄搜尋詞;`品牌 + 品類 + 規格` 仍只對安全品類開放,目標是提升 pending/no_result 候選取得率,同時維持 matcher hard veto 與 `MIN_MATCH_SCORE` 不變。
|
||||
- **V10.570 PChome 身份 / 報價證據契約**: `score_marketplace_match()` 現在會在 `match_diagnostic_json` 內輸出 `identity_evidence` 與 `offer_evidence`,把品牌、品類、identity anchor、型號、規格、入數、variant guardrail 與價格 offer 拆層保存。`competitor_intel_repository` 會把這些證據轉成 `difference_highlights` 與 decision envelope 的 identity / offer evidence,讓覆核頁、PPT、OpenClaw、Webcrumbs 與 Telegram 摘要都能理解「為何同款 / 為何不同 / 價格只是報價證據不是身份證據」。
|
||||
- **V10.569 Webcrumbs 比價信封摘要串接**: `build_webcrumbs_marketplace_host_data()` 讀取 `fetch_competitor_review_queue()` 後統一走 `summarize_review_decision_envelopes()`,在 host data payload 輸出 `reviewDecisionBrief`,並於 metadata 增加 `review_queue_count`、`hitl_count`、`auto_execute_blocked_count` 與 `decision_envelope_source`。Webcrumbs / Shared UI 現在和 Telegram、OpenClaw、PPT 共用同一份 PChome 覆核信封摘要,仍維持只讀、不呼叫 LLM、不抓外站、不寫 DB;同版收錄 `docs/guides/external_professional_benchmark.md` 作為外部專業做法週巡檢落地準則入口。
|
||||
- **V10.568 價格類決策信封專業 brief**: `decision_envelope` 的價格 / PChome 覆核事件在 Telegram EventRouter 直送時,改以「標的、價格證據、比對證據、人工下一步」四段式排版呈現,保留 `momo:eig:` 忽略按鈕且不進 L1/L2 AI 重摘要。`competitor_intel_repository` 同步在 review queue 信封 subject 補上 `momo_price` / `competitor_price`,讓 Telegram、PPT、Webcrumbs 與 AI 摘要可共用同一份價格證據,不再各自補查或重組。
|
||||
- **V10.567 MCP 市場洞察 GCP-only fallback**: `MCPCollectorService._ollama_topic_fallback()` 改成只使用 GCP-A / GCP-B Ollama,失敗後保守回本地 fallback,不再把市場洞察長分析轉嫁到 111。預設 timeout 收斂為 25 秒、`num_predict` 收斂為 500,避免 Elephant Alpha 在 GCP-A/GCP-B 短暫不可用時撞 60 秒總上限或造成 111 負載尖峰;Gemini 仍維持 `GEMINI_API_HARD_DISABLED=true` 預設硬封鎖。
|
||||
- **V10.565 PChome 覆蓋率操作建議**: 補強 `/api/ai/pchome-match/backfill/status`,將低覆蓋率拆成 `operation_backlog`:刷新舊 identity、重評近門檻、補抓未配對、人工覆核、單位價覆核與過期搜尋救援預覽;並新增 `recommended_next_action`,Dashboard 狀態摘要會直接顯示建議下一步,避免使用者只看到低覆蓋率卻不知道該按哪條產線。
|
||||
- **V10.563 正式 preview 假可救候選收斂**: 針對正式 `retryable_candidate_preview` 露出的 M.A.C 蜜粉與 SAUGELLA 菁萃潔浴凝露案例補 guard。M.A.C 單邊明確色號(如 `#絕絕紫`)會進 `variant_selection_review`,維持 `true_low_confidence`;SAUGELLA 潤澤 / 日用型 / 加強 / 黃金女郎型互斥,直接 hard veto,避免同品線不同私密清潔款式被當成 recoverable low_score。
|
||||
- **V10.566 市場情報 Professional Source Governance**: 新增 `/api/market_intel/mcp_professional_source_governance`、preview service/gates/sample 與市場情報頁卡片,將 robots/REP、sitemap/lastmod、JSON-LD / schema.org structured data、canonical URL、rate limit、公開資料邊界、provenance、snapshot hash 與 idempotency key 納入 source contract。此 gate 只審核操作員治理摘要,不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不掛 scheduler;deployment readiness 同步新增 preview-safe 檢查與 production smoke target。
|
||||
- **V10.561 PChome 比價補強前端分段回饋**: Dashboard 的 PChome 操作卡改名為「比價補強產線」,手動按鈕與確認文案同步說明三段流程;結果摘要會顯示刷新、重評、補抓各自的 matched/total,讓操作員能判斷覆蓋率改善來自舊 identity 新鮮度回補、近門檻 matcher 回刷,或 pending 商品 fresh search 補抓。
|
||||
- **V10.560 手動 PChome 比價補強三段式串接**: `/api/ai/pchome-match/backfill` 與每日 scheduler 口徑對齊,手動執行時先小批次刷新過期 `identity_v2`,再跑近門檻候選重評,最後補抓高優先未配對商品。回傳結果新增 `stale_identity_refresh` 分段統計,讓後續 Dashboard、簡報與 AI 決策能區分覆蓋率改善來自舊 identity 新鮮度回補、matcher 回刷,還是 fresh search 補抓。
|
||||
- **V10.559 retryable 有效身份新鮮度收斂**: `_fetch_retryable_candidate_skus()` 的既有 identity 阻擋條件改成只接受 `cp.expires_at > CURRENT_TIMESTAMP`,不再讓 `expires_at IS NULL` 的未知新鮮度舊配對壓住近門檻候選回刷。未知新鮮度仍由 expired identity refresh / recovery 路徑處理,最後寫入仍必須通過現行 matcher、hard-veto、auto write safety 與 stronger existing production match 保護。
|
||||
- **V10.558 legacy focused identity reason 回刷補漏**: `_fetch_retryable_candidate_skus()` 在 V10.557 的具名 identity guard 之外,補上歷史資料缺 marker 的情境:舊 attempt 若沒有新版 `focused_exact_total_price_safe`,但已有具名 `focused_exact_identity_*` 且該 identity 屬於 matcher total-price safe set,並且舊分數已達全域 `MIN_MATCH_SCORE`,可進近門檻重評。仍要求無 hard veto、`exact_identity`、無 commercial / variant / count / bundle 阻擋,最後由最新版 matcher 決定是否能寫正式價差。
|
||||
- **V10.557 focused reason-based 回刷具名 identity guard**: V10.555 的結構化 reason 回刷再收緊,`_fetch_retryable_candidate_skus()` 不只要求 `focused_exact_total_price_safe`,還必須同時命中一條具名 `focused_exact_identity_*` 且該 identity 來自 matcher 的 total-price safe set。這避免只有總開關、缺少身份線索的舊 attempt 被納入回刷;rom&nd、Solone、Summer’s Eve 等 review-only focused line 仍被測試鎖在自動價差線外。
|
||||
- **V10.556 GCP-B model fallback 防 111 過載**: `OllamaService.generate()` 現在會在 GCP-B 對 coder/large 模型使用 host-compatible fallback(預設 `gemma3:4b`),避免 GCP-B 缺 `qwen2.5-coder:7b` 時直接被標成 unhealthy 並把流量推到 111。HTTP 404 且訊息為 model not found 時視為模型缺失,不再 mark 整台 host unhealthy;其他 HTTP / timeout 仍照舊標 unhealthy。主機順序仍是 GCP-A → GCP-B → 111。
|
||||
- **V10.555 focused total-price reason-based 回刷窄門**: `_fetch_retryable_candidate_skus()` 新增結構化 diagnostics reason 回刷入口,舊 attempt 若已帶 `focused_exact_total_price_safe` 且命中 matcher 的 `FOCUSED_IDENTITY_TOTAL_PRICE_REASONS`,即可進近門檻重評,不再完全依賴手寫商品名 SQL。此路徑仍要求分數下限、無 hard veto、`exact_identity`,並套用 commercial / variant / count / bundle 等阻擋理由;rom&nd、Solone、Summer’s Eve 等 review-only focused line 不在 total-price reason set,仍不會被推入自動價差。
|
||||
- **V10.554 香氛 / 精油 focused exact 回刷接線**: Herb24 晨霧純精油擴香儀黑色、Pavaruni 40 香味 10ml 精油、Pavaruni 20 香味 450g 香氛蠟燭、Derma 大地有機植萃護膚油 150ml 已明確列入 matcher 的 `focused_exact_total_price_safe`,並接入 `_fetch_retryable_candidate_skus()` 的近門檻舊候選回刷入口。SQL 仍要求 `low_score / refresh_low_score / true_low_confidence`、分數下限、無 hard veto、`exact_identity`,且排除變體、商業條件與件數衝突;Laundrin、好物良品融蠟燈、Yuskin 等帶人工覆核訊號的品線不納入本輪自動回刷。
|
||||
- **V10.553 current PPT/AI 比價結果查詢瘦身**: `fetch_competitor_comparison_results()` 不再用 `ROW_NUMBER() OVER (PARTITION BY p.id ...)` 掃 `price_records` 取得 current/latest MOMO 價格,改為 `JOIN LATERAL` 逐商品取最新價;有指定 `end_date` 的歷史報表仍把 `pr.timestamp < DATE(:end_date) + INTERVAL '1 day'` 放在 lateral 子查詢中,保留「截止日前最新 MOMO 價」語意。這降低簡報、OpenClaw/AI payload 與比價匯出在大量價格紀錄下的查詢成本。
|
||||
- **V10.552 決策查詢新鮮度口徑收斂**: Top competitor risks、PChome review queue、review sample 與 current PPT/AI 比價結果全部改成只吃 `cp.expires_at > CURRENT_TIMESTAMP` 的有效 PChome identity_v2 價格,不再把 `expires_at IS NULL` 當作有效現價。未知新鮮度現在只作 coverage 診斷與刷新入口,不會被用來產生價格風險、簡報、AI 決策或從覆核隊列中排除。
|
||||
- **V10.551 未知新鮮度刷新與補抓排序收斂**: `expires_at IS NULL` 的 identity_v2 價格現在會被 `_fetch_expired_identity_skus()` 與 `_fetch_expired_identity_recovery_skus()` 視為需要刷新 / 可搜尋救援的未知新鮮度資料,避免 V10.549 已排除出可決策覆蓋率後卻沒有刷新入口;兩條路徑都改用 `JOIN LATERAL` 取最新 MOMO 價,不再做 product-wide window scan。`_fetch_unmatched_priority_skus()` 同步改為 lateral latest price,且把低風險 `no_result / refresh_no_result` 排到補抓前段,讓安全召回詞優先投入最可能回收的 SKU。
|
||||
- **V10.550 安全搜尋召回詞補強**: `competitor_price_feeder` 在既有精準搜尋詞之外,對低風險穩定品類補上一組 `品牌 + 品類` recall keyword,提升 `no_result / refresh_no_result` 找到候選的機率;高 variant 風險商品如美甲片、指甲油、唇彩、香氛/精油、粉底、防曬與含任選/色號/款式/香味的商品不走通用召回。DASHING DIVA 仍保留既有 line-specific recall 與 PChome sort fallback;本次不更動 `MIN_MATCH_SCORE`、hard veto、auto write safety 或 stronger existing match 保護。
|
||||
- **V10.549 比價新鮮度 KPI 口徑收斂**: `fetch_competitor_coverage()` cache 升到 v10,`expires_at IS NULL` 不再混入 `fresh_matches` / `decision_ready_rate`,改拆成 `unknown_freshness_matches` 與 `unknown_freshness_rate`,讓「可用比價覆蓋率」只代表有明確未過期時間的 identity 價格。Dashboard、daily、growth 同步顯示未知新鮮度與「未形成有效身份配對」,第一屏資料時間改看最新有效 PChome 價格抓取,並把價格方向文案改為 `PChome 價格壓力` / `MOMO 價格優勢`。
|
||||
- **V10.548 focused exact 舊候選回刷接線**: `_fetch_retryable_candidate_skus()` 的 focused true-low / rescore 窄門新增 3W CLINIC 膠原蛋白粉底液 50ml x2、花美水 Moisture/Inclear 1.7g x3、KUSSEN 寶寶益菌屁屁膏 50ml 3 入、Lab52 齒妍堂嬰幼兒 / 汪汪隊牙刷 2 入。這些品線在 matcher 測試中已是 `exact / total_price / price_alert_exact`,本次只讓舊 `true_low_confidence` / `rescore_accepted_current` 候選能被新版 matcher 重新判斷;仍不放寬 `MIN_MATCH_SCORE`、hard veto、auto write safety 與 stronger existing match 保護。
|
||||
- **V10.547 單位價覆核洞察與證據鏈保留**: `manual_unit_price_required` 現在會和 `unit_comparable` 一樣重新產生單位價比較,並轉成 `unit_price_insight`,明確標示 PChome 或 MOMO 哪邊單位價較低、差距百分比、嚴重度與操作建議;Dashboard 覆核卡、商品列、決策信封與 OpenClaw/PPT 摘要都可讀到這個訊號。人工覆核寫回 `competitor_match_attempts` 時也會在欄位存在時保留原始 `match_diagnostic_json`、`comparison_mode`、`hard_veto`、`diagnostic_codes`,`competitor_match_reviews.candidate_diagnostic` 同步附帶 JSON 證據,避免人工閉環後只剩狀態文字。
|
||||
- **V10.546 近門檻舊候選回刷隊列補漏**: `run_retryable_candidate_revalidation()` 的候選來源不再只看每個 SKU 最新一筆 attempt。新增 `legacy_unmasked_attempt`,當最新狀態是 `no_result` / `refresh_no_result` / `expired_match` 時,可回撈同 SKU 早期 `low_score`、`recoverable_low_score`、`true_low_confidence` 或 `rescore_accepted_current` 的近門檻候選,再交給最新版 matcher 重評。此入口仍要求 candidate product id、分數下限、無 hard veto、`exact_identity`,且不打開人工否決、單位價、identity_veto 或 protected existing match,避免為了覆蓋率破壞安全邊界。
|
||||
- **V10.545 Dashboard 比價覆蓋率口徑收斂**: 商品看板與 PChome 覆核頁把「身份覆蓋率」與「可用比價覆蓋率」拆成明確欄位,coverage cache 更新為 v9 並回傳 `identity_coverage_*`、`pending_identity_count`、`stale_identity_count`、`last_decision_ready_crawled_at`。覆核 KPI 改只計算真正待處理狀態,人工否決 / 人工單位價 / 需補研究另列 `manual_closed_count`,避免人工閉環候選被混進待審總數。Dashboard 的 PChome competitor map 也改成只取新鮮、有效價格、identity_v2 的最新 row,資料新鮮度改看可用比價 row,不再被無效或低信心抓取紀錄撐高。
|
||||
- **V10.544 變體安全與 YES 工具線收斂**: 延續近門檻 `low_score` 救回,但把安全邊界補得更硬。新增 YES 德悅氏指甲工具精準線,只有同品牌、同工具線、同尺寸且同亮面/霧面/可收納/三面等關鍵款式時才進 `total_price`,並接入 revalidation SQL。同步新增未知香味差異與無型號指彩色名差異 hard veto:MUJI / COCODOR 不同香型、OPI 無型號不同色名不再被高分誤配;HOOOME 暖燈陶瓷/玻璃/水晶/金屬等材質差保留人工覆核。搜尋詞對護手霜、擴香瓶、無型號指彩優先帶上香味/色名,提升 crawler 找到真同款候選的機率。
|
||||
- **V10.543 rescore accepted 窄門回刷與高信心線補強**: `run_retryable_candidate_revalidation()` 追加 `rescore_accepted_current` 窄門,只允許已進人工池且命中具名 focused exact 品線的候選重新評分,仍由 matcher 判定是否可寫正式價差。新增 SK-II 青春露 330ml 兩入、AMIINO 安美諾美白修護霜 30ml、YES 腳指甲剪刀 10.5cm、YES 極細指甲緣硬皮剪刀 9cm 的 total-price 安全線;同時新增指甲油型號衝突防線,ANNY `A10.074.60` vs `A10.500`、OPI 不同 `ISL...` 型號都會 hard veto,不會為了拉覆蓋率誤配色號。
|
||||
- **V10.542 可用比價覆蓋率口徑拆分**: `fetch_competitor_coverage()` 新增 `decision_ready_matches` / `decision_ready_rate`,以「高信心 identity 且價格仍新鮮」除以 ACTIVE 商品數,和 `match_rate`(身份覆蓋)及 `fresh_match_rate`(已配對中的新鮮率)分開。Dashboard 第一屏改顯示可用比價覆蓋率,daily / growth / Webcrumbs / OpenClaw payload 同步輸出,避免使用者把舊截圖的低價格可用率、身份覆蓋率與新鮮率混成同一個 KPI。
|
||||
- **V10.541 高信心覆核池 exact 線再收斂**: 從正式覆核頁抓到多筆 `rescore_accepted_current` 仍卡人工的同款:The Ordinary 咖啡因 EGCG(MOMO 單側漏 30ml)、Natures Care 綿羊油同入數、TOMOON 指甲剪同 L/S 尺寸、HH 私密潔淨露+私密衣物手洗精雙 200ml、SEBAMED 護潔露 200ml x2、YES 德悅氏 9cm 剪刀。新增 focused total-price 規則與 revalidation SQL;TOMOON / O.P.I 仍要求同型號或同尺寸,避免跨款式誤配。
|
||||
- **V10.540 O.P.I 指彩精準型號救回**: 正式覆核頁面顯示多筆 O.P.I 類光繚 / 如膠似漆指彩因 score 0.76~0.83 停在 `rescore_accepted_current` 人工池,但雙方都有明確 `ISL...` 型號(如 ISLL87、ISLL00、ISLN25)。新增 `opi_gel_polish_exact_model` focused total-price 規則:必須同品牌、同類光繚指彩線、共享型號 token;不同型號/色號不自動通過。同步接入 revalidation SQL,讓舊 `true_low_confidence` 指彩候選可批次重評。
|
||||
- **V10.539 任選 catalog focused exact 與 commercial 防線**: 新增 FLORTTE 水果沙拉眼線液筆 0.5ml、露得清護手霜 56g 無香/有香、Kanebo ALLIE 持采亮化 UV 防曬水凝乳 60g 的 focused total-price 規則,條件是雙方同品牌、同品線、同規格且任選 catalog 語意一致;同步接入 `run_retryable_candidate_revalidation()`。同時修正 focused bypass:若存在 `commercial_condition_gap`(例如即期品),不得移除 `variant_selection_review`,避免商業狀態差異被自動寫入正式價差。
|
||||
- **V10.538 ai_calls provider CHECK 對齊**: 正式 scheduler Hermes/Ollama 全失敗時會以 `provider=ollama_other` 記錄未知/未選定 host,但 `ai_calls.chk_ai_calls_provider` 舊白名單未包含此 telemetry bucket,導致觀測寫入再撞 CHECK。新增 migration 043 放行 `ollama_other`,並讓 `ai_call_logger` 將空值、unknown、非白名單 provider 字串正規化為允許值;`ollama_other` 僅作遙測分類,不是模型路由目標。
|
||||
- **V10.537 focused exact revalidation queue 接線**: V10.536 只補 matcher 規則會影響新抓取,但無法批次回收既有 `true_low_confidence` 舊候選。`run_retryable_candidate_revalidation()` 新增 focused true-low 窄門:花美水 Relax、St.Clare 私密呼呼、BIOPEUTIC 果酸、台塑生醫、Elizabeth Arden 與理膚寶水若無 hard veto 與型別/款式/香味/件數/組合阻擋,可進重評;`variant_selection_review` 僅在這些具名安全線允許重跑,其他人工池不打開。
|
||||
- **V10.536 高分 true_low_confidence focused exact 救回**: production 抽樣顯示剩餘未覆蓋集中在 `identity_veto` 與 `true_low_confidence`,其中部分 1.0 分樣本是同品牌、同品線、同規格/同組合,但因多件組或護唇/私密護理類型辨識保守而停在 manual review。新增花美水 Relax、St.Clare 私密呼呼、BIOPEUTIC 果酸、台塑生醫嬰兒沐浴洗髮、Elizabeth Arden 八小時護唇膏、理膚寶水全面修復潤唇膏的 focused total-price 規則;不放寬 `MIN_MATCH_SCORE`,也不移除色號/香味/款式防線。
|
||||
- **V10.535 ElephantAlpha price trigger 查詢瘦身**: 正式 scheduler 日誌顯示 `price_drop_alert` trigger 對整張 `price_records` 做 `DISTINCT ON` 最新價造成 statement timeout。`price_drop_alert`、`market_opportunity` 與 EA DB evidence prefetch 改為先篩最近有效 PChome identity_v2 競品,再用 `JOIN LATERAL` 只查該 SKU 最新 MOMO 價格,保留 match_score/tags/diagnostic evidence 給 Telegram HITL,不再用全表最新價子查詢。
|
||||
- **V10.534 rescore accepted gate 收緊與語意修正**: 正式 96 筆 `rescore_accepted_current` 盤點顯示多數仍是 `manual_review / identity_review`,且有 2 筆 `no_match / none / suppress` 混入。收緊 `classify_match_attempt_row()`:`no_match`、`price_basis=none`、`alert_tier=suppress` 不得 gate pass;新增 `--retract-unsafe-accepted` 可把既有 unsafe accepted 退回 `true_low_confidence`。Dashboard / daily / growth / OpenClaw 文案改成「重算待人工覆核」,明確表示仍需人工確認身份後才可寫正式價差。
|
||||
- **V10.533 ElephantAlpha legacy OpenClaw advisory 相容**: 正式 scheduler 日誌出現 `Unrecognized step: agent=openclaw action=generate_dynamic_pricing_strategy`,屬於舊協調器把建議型策略文字放進 execution plan。執行器現在將 `generate_dynamic_pricing_strategy` 納入既有 OpenClaw advisory no-op 清單,只記錄 skipped,不觸發 circuit breaker,也不轉成自動調價或外部呼叫。
|
||||
- **V10.532 coverage / review queue 口徑對齊**: V10.531 materialize 96 筆 `rescore_accepted_current` 後,DB 最新狀態正確,但 `/api/ai/pchome-match/backfill/status` 的 `rescore_accepted_count` 仍為 0。原因是 coverage 的 `attempt_status` 統計只看「完全沒有 identity」商品,而 review queue 看的是「沒有新鮮有效 identity」商品。改為以 `fresh_competitor` 排除條件統計,讓 stale identity 的重算可採用待審能正確上屏;正式價差表仍未被 rescore materialize 寫入。
|
||||
- **V10.531 PChome 安全 exact 規則補強**: production refresh 顯示大量舊 identity 不是分數不足,而是被多件組 / 護唇品 variant 防線過度保守地擋在 `identity_review`。新增 `safe_multi_component_exact_total_price`:同品線、同規格、同數量且商品型別完全對齊、無 variant / count / bundle / commercial / unit-price 阻擋時,才可進 `exact / total_price / price_alert_exact`;另補 DHC 純欖護唇膏 1.5g、FRUDIA 蜂蜜藍莓護唇膏 10g、SEBAMED 嬰兒護唇膏 4.8g x2、理膚寶水滋養修護潤唇膏 4.7ml focused total-price。回歸測試保留 HH 混合組、TS6 香味衣物手洗精、粉底色號與蠟燭 catalog 不自動放行。
|
||||
- **V10.530 retryable preview 輕量化與 recover-stale 安全閘**: 正式站 profiling 證實 `/api/ai/pchome-match/backfill/status` 剩餘瓶頸在 `revalidation_preview`,約 12 秒;`_fetch_retryable_candidate_skus()` 改為先取每個 SKU 最新 attempt 並縮小到可重評候選,再 `JOIN LATERAL` 取單一最新 MOMO 價,不再對全量 `price_records` 做商品 window scan。正式 smoke 也顯示過期 identity fresh-search rescue 小批次 5 筆耗時約 109 秒且 0 筆成功,因此 Dashboard 移除「救援過期 40 筆」按鈕,只保留 `stale_recovery_preview` 只讀觀測;後端 `/api/ai/pchome-match/recover-stale` 保留但需明確設定 `PCHOME_STALE_RECOVERY_ENABLED=true` 才能執行,避免低成功率慢任務拖住正式 worker。
|
||||
- **V10.529 recover-stale 名稱風險擋詞補強**: 過期 identity 搜尋救援新增 `+`、`x2`、`*2` 等組合暗示,以及湛藍、麋香、海洋、玫瑰、薰衣草、生理呵護、日用型、清爽、潤澤等常見變體 / 香味 / 版本詞;避免同品牌同規格但不同香味、不同膚感或不同使用情境的 stale pair 進慢速 fresh search。
|
||||
- **V10.528 recover-stale preview 輕量化**: V10.527 的救援隊列在正式站 preview 時曾造成 status API 超時。改為雙階段篩選:SQL 從過期 `competitor_prices` 小集合出發,只做 identity_v2、過期、exact/total_price/price_alert_exact 等必要條件並限制候選量,再用 `JOIN LATERAL` 取 ACTIVE 商品最新 MOMO 價;variant / catalog / commercial condition / 高風險名稱訊號改在 Python 對小樣本過濾,避免 `/api/ai/pchome-match/backfill/status` 因全量 price_records、JSONB + regex preview 查詢拖垮。
|
||||
- **V10.527 PChome 過期 identity 搜尋救援隊列收斂**: V10.526 production smoke 發現直接對全部過期 `identity_v2` 做 rescue 會把香氛 / 色號 / 目錄款 / 商業狀態差異等人工覆核型 stale pair 送進慢速 fresh search,20 筆耗時 361 秒且 0 筆成功。新增 `_fetch_expired_identity_recovery_skus()` 作為救援專用隊列,只收既有正式診斷為 `exact_identity / total_price / price_alert_exact` 且無 variant、catalog、commercial condition、count、bundle、unit-price 等阻擋理由的舊配對;名稱含任選、多款、香味、色號、即期、融燭燈、香氛蠟燭等高風險訊號先排除。
|
||||
- **V10.526 PChome 重評預覽與過期 identity 搜尋救援**: `/api/ai/pchome-match/backfill/status` 新增 60 秒快取的 `revalidation_preview` 與 `stale_recovery_preview`,Dashboard 補抓產線顯示「可重評 / 窄門 / 可救援」數字;兩個 preview 都只讀 DB,不啟動 PChome 搜尋、不呼叫 LLM、不寫 `competitor_match_attempts` 或正式價格表。另新增 `/api/ai/pchome-match/recover-stale` 與「救援過期 40 筆」按鈕,對過期 `identity_v2` 先查既有 product_id,只有在舊 ID 缺失或低分時才走受控 fresh-search recovery,最後仍經 hard veto、auto price write safety 與 overwrite protection 才能寫入正式比價。
|
||||
- **V10.525 高分 review-gated exact 舊候選窄門重評**: `run_retryable_candidate_revalidation()` 保持主戰場為 `low_score / refresh_low_score / recoverable_low_score`,只額外收 Beauty Foot、KAMERIA、TS6、Vaseline 這批已由 V10.523 補 focused exact 規則的 `true_low_confidence` 舊候選。入口要求舊分數 >= 0.95、仍為 `exact_identity`、具備 `strong_exact_spec_match`,且不得含 `commercial_condition_gap`、variant、count、bundle、refill 等阻擋理由;讓已驗證真同款可被回刷,不把整個人工審核池自動打開。
|
||||
- **V10.524 PChome 過期價格刷新手動入口**: 商品看板 PChome 補抓產線新增「刷新過期 120 筆」按鈕與 `/api/ai/pchome-match/refresh-stale`,背景執行既有 `run_expired_identity_refresh()`,只刷新已建立 `identity_v2` 的 PChome product_id,不跑 fresh search recovery、不呼叫 LLM;完成後重算 AI 挑品並清除 Dashboard / competitor intel cache,讓 `stale_matches` 從觀測指標變成可直接操作的任務。
|
||||
- **V10.523 高分真同款 exact identity 比價規則補強**: 針對正式環境反覆出現、分數已達 1.0 但因 `multi_component_pair` 或 variant review gate 被留在人工審核的真同款,補 Beauty Foot 足膜、KAMERIA 積雪草足膜、TS6 蜜愛潤滑液 / 蜜桃煥白凝膠 / 極淨白+煥白組合、Vaseline 嬰兒高純修護凝膠 focused exact identity。這些案例只有在品牌、品線、容量 / 重量與入數完全對齊時才進 `exact / total_price / price_alert_exact`;TS6 香味衣物手洗精等款式敏感商品仍維持 `manual_review`,全域 `MIN_MATCH_SCORE` 與 overwrite 保護不變。
|
||||
- **V10.522 PChome backfill status 附帶 coverage snapshot**: `/api/ai/pchome-match/backfill/status` 在背景任務狀態外同步回傳 read-only `fetch_competitor_coverage()` 摘要,包含身份覆蓋、新鮮率、過期價格與待補抓數;Dashboard 補抓產線即使尚無最近 run result,也會顯示「身份覆蓋 / 新鮮 / 待刷新 / 待補抓」,讓操作員能分辨下一步該跑 expired identity refresh 還是 unmatched backfill。此狀態端點不寫 DB、不呼叫 LLM、不抓外站。
|
||||
|
||||
### 2026-05-31:Webcrumbs 共用 UI Runtime 與市場情報 writer approval
|
||||
- **V10.521 比價新鮮度 stale 指標上屏**: 首頁比價監控總覽、PChome 補抓產線、daily 競價覆蓋與 growth 比價資料品質同步顯示 `stale_matches` / 價格過期數,讓操作員能分清「已確認同款但價格待刷新」與「尚未找到身份配對」,不再只看到新鮮率下降。過期 identity refresh 也優先刷新 `total_price / price_alert_exact` 的正式價差配對,讓最能進決策與告警的舊價格先回新鮮。
|
||||
- **V10.520 PChome 過期價格刷新快慢路徑拆分**: `run_expired_identity_refresh()` 改為只刷新已確認 `identity_v2` 的既有 PChome product_id;若 product_id 已查不到或回傳後低分,不再同步跑慢速 fresh search recovery,而是記錄 `refresh_no_result` / low-score 並交給 `run_retryable_candidate_revalidation()` 的近門檻救援路徑。這能避免正式回刷 500+ 筆時被少數缺失 ID 拖到長時間卡住,讓價格新鮮度批次回升更可控。
|
||||
- **V10.519 Webcrumbs host data metadata 對齊新覆蓋率口徑**: Webcrumbs host data metadata 同步輸出 `fresh_match_count`、`fresh_match_rate`、`stale_match_count` 與 `pending_match_count`,讓共用 UI / 其他專案 proxy 能分清身份覆蓋與價格新鮮度,不再只看到舊的 matched_count / coverage_rate。
|
||||
- **V10.518 PChome 覆蓋率與新鮮度拆分**: 修正比價監控總覽把價格 TTL 過期誤算成「未覆蓋」的產品口徑,`fetch_competitor_coverage()` 現在分開回報 `valid_matches`(identity 覆蓋)、`fresh_matches`(價格新鮮)、`stale_matches` 與 `fresh_match_rate`;首頁、業績與成長頁同步顯示身份覆蓋與價格新鮮。PChome 快取 TTL 預設由 6h 改 48h,並將每日 expired identity refresh / retryable / unmatched limits 改為環境變數,預設提升到 1200 / 240 / 240,避免已建立 identity 的商品因刷新量不足被長期視為無覆蓋。
|
||||
- **V10.517 PChome near-threshold 比對 hotfix**: 新增 Lab52 齒妍堂汪汪隊嬰幼兒牙刷 2 入組 focused exact identity,讓真同款可進 `exact / total_price / price_alert_exact`;同時補 Les nez 香氛融蠟燈款式選擇 gap 與 Time Leisure 香薰蠟燭香味 gap,將不同款式 / 單側香味候選留在覆核或 veto,不讓它們進 recoverable 自動回刷。測試鎖住 Dashing Diva、Pavaruni、Recipe Box、Lactacyd 與 feeder recoverable 邊界。
|
||||
- **V10.516 Webcrumbs host data 授權回歸測試**: 新增 Flask runtime 測試,直接重現 `DISABLE_LOGIN=true` 下 `/api/webcrumbs/marketplace-host-data` 必須回 401 且不得組裝真實 host data;同時鎖住 `X-Internal-Key` 與登入 session 可取得敏感 seed、未授權 `/webcrumbs` 只注入 `auth_required` 空狀態,避免後續改動再讓 public 診斷頁洩漏 MOMO/PChome SKU 與價差。
|
||||
- **V10.515 Webcrumbs host data 硬性授權**: 發現正式環境一般 `@login_required` 可能因 `DISABLE_LOGIN=true` 放行後,為 `/api/webcrumbs/marketplace-host-data` 與 `/webcrumbs` inline seed 加上獨立授權判斷;只有登入 session 或 `X-Internal-Key` 可取得真實 SKU/價差,未授權時只回 `auth_required` 空狀態,避免 public runtime 診斷頁洩漏正式比價資料。
|
||||
- **V10.514 Webcrumbs host data read-only API**: 新增登入後 `/api/webcrumbs/marketplace-host-data`,回傳與 `/webcrumbs` inline seed 相同的 MOMO/PChome exact 價差 host data contract,供 plugin、QA 與其他專案 proxy 驗證;API boundary 明確標示 `writes_database=false`、`calls_llm=false`、`fetches_external=false`,且只允許 exact / total_price / price_alert_exact 摘要。
|
||||
- **V10.513 外部工具診斷 payload 模組化**: 新增 `services/external_tool_payload_service.py`,把 Metabase/Grist/Webcrumbs 診斷頁 payload 與 Webcrumbs host data 組裝從 `routes/system_public_routes.py` 移出;route 保持 HTTP glue 與 asset proxy,避免 Webcrumbs live plugin 與比價 host data 持續把公開系統 route 撐成大檔。
|
||||
- **V10.512 Webcrumbs MOMO/PChome host data**: 新增 `services/webcrumbs_host_data_service.py`,讓 `/webcrumbs` live plugin preview 復用 `competitor_intel_repository.fetch_top_competitor_risks()` 與 coverage,將 exact / total_price / price_alert_exact 的只讀價差摘要轉成 `StockPlatformSharedUI.marketSnapshot` / `aiCandidate`;不呼叫 LLM、不抓外站、不寫 DB,無風險或讀取失敗時仍輸出安全空狀態。
|
||||
- **V10.511 Webcrumbs host data 安全空狀態**: `/webcrumbs` 會注入 `StockPlatformSharedUI.marketSnapshot` / `aiCandidate` 的診斷空資料,避免 Shared UI Hub plugin 因資料源未接入而 fallback 顯示假市場數字、假 AI 候選或假信心分數。
|
||||
- **V10.510 Webcrumbs live plugin 試點**: `/webcrumbs` 診斷頁不再只顯示 runtime 設定,新增 `StockPlatformSharedUI.allowedPluginUris` 與同源 `/webcrumbs-assets/plugins/finance.market-ticker-strip/0.1.0`、`finance.ai-candidate-card/0.1.0` live plugin embed,讓 shared-ui loader、plugin asset proxy 與頁面初始化時序能在 momo-pro 內直接驗收。
|
||||
- **V10.509 市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval Writer Preflight gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight` 與 UI preview,只審核 human approval 通過後的 operator writer preflight 摘要;要求 approval linkage、writer_preflight_id、target operation、row count、dedupe keys、approved decision 到 target review_state 的逐列映射、decision/approval/preflight evidence refs、artifact paths、matched row exact-identity/variant/overwrite guard 與 operator confirmation 對齊,且 API 不讀 token、不執行 CLI、不開 DB、不寫 preflight/approval/decision/match、不更新 review_state、不補 queue、不掛 scheduler,只放行到後續 CLI review / run package 設計。
|
||||
- **V10.508 Webcrumbs 同源 asset proxy 收斂**: 正式頁面 runtime 預設改走 `/webcrumbs-assets/loader/webcrumbs-compatible-loader.js`,由 momo-pro 代理 188 Shared UI Hub 的 allowlist asset path(`loader/`、`plugins/`、`demo/`),避免 `webcrumbs.wooo.work` 公網 TLS、Basic Auth 或入口轉發尚未完成時造成頁面 script 載入失敗;`/webcrumbs` 診斷頁同步顯示 asset upstream,`WEBCRUMBS_PLUGIN_BASE_URL` 預設改為同源 `/webcrumbs-assets/plugins`。
|
||||
- **V10.507 Webcrumbs 共用 UI Runtime 接入**: 新增 `WEBCRUMBS_*` 設定、`/webcrumbs` 診斷頁、全站 `ewoooc_base.html` runtime script 載入守門與側欄入口;runtime 僅允許自架固定版本 URL,禁止正式環境使用官方 `@latest`,並新增 `docs/guides/webcrumbs_shared_runtime.md` 與 ADR-037 記錄共用 microfrontend/plugin loader 邊界。
|
||||
- **V10.506 市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_review_decision_approval` 與 UI preview,只審核 review decision 通過後的 operator human approval 摘要;要求 decision linkage、approval identity、target table、row count、dedupe keys、`approved_for_writer_preflight` approval result、decision/approval evidence refs、artifact paths、matched row exact-identity/variant/overwrite guard 與 operator confirmation 對齊,且 API 不讀 token、不執行 CLI、不開 DB、不寫 approval record、不寫 decision record、不更新 review_state、不寫 match result、不補 queue、不掛 scheduler,只放行到後續 writer preflight 設計。新 endpoint 拆到 `routes/market_intel_mcp_review_routes.py`,避免既有 MCP run route 繼續膨脹。
|
||||
|
||||
### 2026-05-24:PChome 近門檻身份回收第二輪
|
||||
- **V10.505 市場情報 MCP Fetch Candidate Queue Writer Review Decision gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_review_decision` 與 UI preview,只審核 review inventory 通過後的 operator candidate queue review decision 摘要;要求 decision identity、target table、row count、dedupe keys、`needs_review` 現態、允許決策集合、evidence refs、matched row exact-identity/variant/overwrite guard 與 operator confirmation 對齊,且 API 不讀 token、不執行 CLI、不開 DB、不寫 decision record、不更新 review_state、不寫 match result、不補 queue、不掛 scheduler,只放行到 decision approval / writer preflight 設計。
|
||||
- **V10.504 市場情報 MCP Fetch Candidate Queue Writer Review Inventory gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_review_inventory` 與 UI preview,只審核 writer review handoff 通過後的 operator read-only candidate queue inventory 摘要;要求 handoff identity、target table、row count、dedupe keys、review_state、artifact paths、read-only query result、missing/duplicate rows 與 operator confirmation 對齊,且 API 不讀 token、不執行 CLI、不開 DB、不寫 queue、不更新 review_state、不做 inventory query、不掛 scheduler,只放行到後續人工 candidate queue review。
|
||||
- **V10.503 市場情報 MCP Fetch Candidate Queue Writer Review Handoff gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_review_handoff` 與 UI preview,只審核 post-closeout inventory review 通過後的 operator candidate queue review handoff 摘要;要求 inventory linkage、handoff identity、target table、row count、artifact paths、review contract、forbidden API actions 與 operator confirmation 對齊,且 API 不讀 token、不執行 CLI、不開 DB、不寫 queue、不更新 review_state、不掛 scheduler,只放行到人工 candidate queue review。
|
||||
- **V10.502 AiderHeal 自動修復診斷鏈修正**: `execute_code_fix()` 改為先檢查 ADR-020 檔案白名單再執行 110 preflight,避免 `tests/...` 這類不得自動修的 finding 被誤報成 `/home/wooo/ewoooc` preflight 失敗;Code Review 完成通知會把全數不在白名單的 finding 標成需人工處理,不再宣稱已觸發 AiderHeal;白名單同步放行 `services/routes/database` 子目錄 Python 檔,preflight 通知帶 stderr/stdout 細節,健康檢查接受正式 `/health` 回傳的 `healthy`。
|
||||
- **V10.501 市場情報 MCP Fetch Candidate Queue Writer Post-Closeout Inventory Review gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_post_closeout_inventory_review` 與 UI preview,只審核 closeout review 通過後的 operator live inventory read-only 摘要;要求 closeout linkage、row count、inventory artifact、closeout review artifact、read-only query result、missing/duplicate rows 與 operator confirmation 對齊,且 API 不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 inventory query、不掛 scheduler,只放行到 candidate queue review handoff。
|
||||
- **V10.500 市場情報 MCP Fetch Candidate Queue Writer Run Closeout Review gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_run_closeout_review` 與 UI preview,只審核 receipt review 通過後的 operator closeout 摘要;要求 receipt linkage、closeout artifact、receipt review artifact、post-closeout inventory plan、writer output / post-write smoke / backup manifest、rollback note 與 operator confirmation 對齊,且 API 不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-closeout query、不掛 scheduler,只放行到 read-only post-closeout inventory review。
|
||||
- **V10.499 市場情報 MCP Fetch Candidate Queue Writer Run Receipt Review gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_run_receipt_review` 與 UI preview,只審核操作員 shell writer run 後貼回的 receipt 摘要;要求 readiness linkage、run package id、候選/dedupe keys、writer output、post-write smoke、backup path 與 operator confirmation 對齊,且 API 不讀 receipt 原文、不讀 token、不執行 CLI、不開 DB、不寫 queue、不做 post-write query、不掛 scheduler,只放行到 closeout review。
|
||||
- **V10.498 市場情報 MCP Fetch Candidate Queue Writer Run Readiness gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_run_readiness` 與 UI preview,只審核 run package review 後的 operator readiness 證據;要求 run readiness artifact、reviewed sample、備份、read-only preflight 與 post-write smoke 路徑安全,並確認 CLI-only、approval token shell-only、無 API/DB/file/scheduler 副作用。API 不產檔、不讀 token、不執行 CLI、不開 DB、不寫 queue、不掛 scheduler,只放行到後續 run receipt review。
|
||||
- **V10.497 市場情報 MCP Fetch Candidate Queue Writer Run Package Review gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_run_package_review` 與 UI preview,只審核 CLI review 後的 operator run package 草案;要求 package identity、artifact manifest、operator shell command sequence、candidate/dedupe keys 與 CLI review 完全對齊,且 API 不產檔、不讀 approval token、不執行 CLI、不開 DB、不寫 queue、不掛 scheduler,只放行到 run readiness review。
|
||||
- **V10.496 市場情報 MCP Fetch Candidate Queue Writer CLI Review gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_cli_review` 與 UI preview,只審核 writer preflight 後的 CLI review 草案;要求 script path、target table、preflight id、payload row count、candidate/dedupe keys 與 command argv 完全對齊,並禁止 `--execute`、`--apply-real-write`、`--approval-token` 進 API payload,且 API 不執行 CLI、不讀 token、不寫檔、不開 DB、不寫 queue、不掛 scheduler。
|
||||
- **V10.495 市場情報 MCP Fetch Candidate Queue Writer Preflight gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_writer_preflight` 與 UI preview,只審核 queue review 後的 writer preflight 草案;要求 `target_table=market_alert_review_queue`、`write_mode=cli_only_later`、dedupe strategy、insert columns、payload rows 與候選 key 完全對齊,且 API 不開 DB、不執行 CLI、不建立 queue、不更新 review_state、不寫 DB、不連外、不掛 scheduler。
|
||||
- **V10.494 市場情報 MCP Fetch Candidate Queue Review gate**: 新增 `/api/market_intel/mcp_fetch_candidate_queue_review` 與 UI preview,只審核 candidate handoff 後的人工 queue review 草案;要求候選 key 完全對齊、`review_state=needs_review`、allowed actions 限人工確認/否決/延後、`queue_write_status=not_persisted`,且 API 不建立 queue、不更新 review_state、不寫 DB、不連外、不掛 scheduler。
|
||||
- **V10.493 市場情報 MCP Fetch Candidate Handoff Review gate**: 新增 `/api/market_intel/mcp_fetch_candidate_handoff_review` 與 UI preview,只審核 parser review 後的候選交接包;要求 source/candidate key 完全對齊、queue policy 維持 `manual_candidate_review` / `preview_only`、候選數維持小批次,且 API 不建立 queue、不寫 DB、不讀 artifact、不連外、不掛 scheduler。
|
||||
- **V10.491 市場情報 MCP Fetch Result Parser Review gate**: 新增 `/api/market_intel/mcp_fetch_result_parser_review` 與 UI preview,只審核操作員 shell parser 後貼回的結構化摘要;API 不讀 artifact、不執行 parser CLI、不抓外站、不寫檔、不開 DB、不掛 scheduler,且會阻擋 raw HTML/body/snapshot、secret/token 欄位與 side-effect flags。
|
||||
- **V10.489 PChome 低分同款人工覆核回收與 gate-pass 風險邊界**: `marketplace_product_matcher` 新增三個窄範圍 focused identity:TS6 超美白香氛誘霜 120g/ml、W 修護保養蝸牛特潤修護面膜 6 片、Derma 大地 Eco 植萃護膚油 2 入。這些樣本只升到 `identity_review / manual_review`,不進 `price_alert_exact`;同版補 Clarins 身體油不同線、命名組合品數量反轉、isLeaf 香型數量不一致 hard veto,HOOOME 大理石暖燈單側設計差留人工覆核。
|
||||
- **V10.488 市場情報 MCP Fetch Run Receipt gate**: 新增 `/api/market_intel/mcp_fetch_run_receipt` 與 UI preview,只審核操作員 shell dry-run 後貼回的 receipt;API 不執行 CLI、不抓外站、不寫檔、不開 DB、不掛 scheduler,且會阻擋 secret/token 欄位與 side-effect flags。
|
||||
- **V10.473 背景 embedding 讀取 host_health skip**: `OllamaService.generate_embedding(..., allow_111_fallback=False)` 會先查最近 `host_health_probes`;若 GCP-A/GCP-B 在 20 分鐘視窗內已由 runtime probe 標成 unhealthy,背景 embedding 直接跳過該節點並開短暫 GCP circuit,不等待 30 秒 timeout、不落 111。DB 讀取失敗時 fail-open 回原本 retry,避免觀測層阻斷 embedding。
|
||||
- **V10.472 GCP Ollama failover rootless 診斷**: 新增 `scripts/ops/diagnose_ollama_gcp_failover.sh` 與 DevOps SOP,可不需 root 檢查 GCP-A/GCP-B/111 direct、110 proxy `11435/11436` 與 GCP-B `bge-m3` runtime。現況確認:GCP-A `22/11434` refused、GCP-B `22/11434` open 但 SSH key denied、GCP-B embed OK、110:11435 502、110:11436 OK;primary 修復需 GCP/SSH 或 110 root 權限。
|
||||
- **V10.471 GCP-B embedding timeout 校準**: GCP-B `bge-m3` `/api/embed` 直接實測約 6.4s / 7.3s / 23.5s,原 `OLLAMA_EMBED_MAX_TIMEOUT=15` 與 host health `OLLAMA_HOST_HEALTH_EMBED_TIMEOUT=8` 會誤判慢但成功的 embedding;預設改為 30s。背景 embedding 仍只跑 GCP-A/GCP-B,不落 111。
|
||||
- **V10.470 Ollama host health 實作探針**: `run_host_health_probe()` 對 GCP-A / GCP-B 在 `/api/tags` 成功後追加短 `bge-m3` `/api/embed` probe,避免 GCP-B 出現 tags/version 正常、但實際 embedding runner 20s timeout 時仍被標 healthy;111 預設不做背景 embedding probe,避免監測任務把 fallback Mac 載入 `bge-m3`。
|
||||
- **V10.469 Background embedding 降級語意修正**: `OllamaService.generate_embedding(..., allow_111_fallback=False)` 在 GCP-A/GCP-B 全失敗時會開啟短暫 failure circuit 並記 WARNING,不再把背景 `bge-m3` 降級熔斷每分鐘寫成 ERROR;同步或允許三主機 fallback 的 embedding 全失敗仍維持 ERROR,保留真正阻塞型故障訊號。
|
||||
- **V10.468 Ollama import-time / embedding 熔斷治理**: `config.OLLAMA_HOST`、`HERMES_URL`、`EMBEDDING_HOST` 舊相容常數改成靜態核准 env reader,不再於 import 時呼叫 `resolve_ollama_host()`,避免 GCP-A/GCP-B 短暫拒連時把 process 常數 freeze 到 111。`generate_embedding(..., allow_111_fallback=False)` 在 GCP-A/GCP-B 都失敗後會開短暫 GCP embedding circuit,避免背景任務每筆重打兩台故障主機;111 仍不承接背景 `bge-m3`。維運盤點曾見 110 proxy 11435/11436 因 GCP 11434 refused 回 502;部署 smoke 時 GCP-B `/api/version` 已恢復 200 並成為動態路由落點,GCP-A 22/11434 仍 refused,後續需以 GCP 權限恢復 primary Ollama 主機或 SSH key。
|
||||
- **V10.467 Focused exact total-price 安全通道**: `marketplace_product_matcher` 新增窄範圍 `focused_exact_total_price_safe` lane,僅針對正式近門檻樣本中同品牌、同品名、同規格/同入數的 3W CLINIC 粉底液 2入、花美水凝膠 3支、The Ordinary 咖啡因 EGCG 30ml、KUSSEN 屁屁膏 3入、Bone 擴香禮盒、1990 融燭燈白色款與 CANMAKE 淚袋盤,讓 `exact/manual_review` 可升到 `exact/total_price/price_alert_exact`;未放寬 `MIN_MATCH_SCORE`,DASHING DIVA、唇彩、香味、色號/款式敏感商品仍維持 variant / veto 保護。Production pilot 已將 SKU `6101639`、`10074951`、`7760902`、`TP00074980000005`、`14774766`、`10142589`、`10262470`、`10262471`、`11308520` materialize 到人工覆核隊列,`true_low_confidence` 802→793、`rescore_accepted_current` 38→47;`6101784` 即期品因商業條件不同仍留在低信心覆核。
|
||||
- **V10.466 Rescore latest-state duplicate 修正與 7 SKU pilot**: `materialize_rescore_accept_reviews()` 的 duplicate 判斷改看最新 attempt,而不是歷史任一 accepted;若後續 crawler 又把同 SKU/候選覆蓋成 `true_low_confidence`,可重新追加 `rescore_accepted_current` 讓 Dashboard latest-state 正確進人工覆核。Production pilot 已將 SKU `14756069`、`11159042`、`13842560`、`8394210`、`15192547`、`10509765`、`10603780` materialize 到人工覆核隊列;`competitor_prices` 目標計數維持 7、`competitor_price_history` 目標計數維持 210,未寫正式價差表。
|
||||
- **V10.465 Embedding GCP fallback 修正**: `OllamaService.generate_embedding(..., allow_111_fallback=False)` 若 resolver 因 unhealthy cache 回 111,會強制改試尚未嘗試的 GCP-A/GCP-B,不再直接 `break` 造成 `tried=[]` 或只試單台 GCP-B;背景 embedding 仍不允許落 111。
|
||||
- **V10.464 Rescore SKU pilot 篩選**: `audit_competitor_match_attempt_rescore.py` 與 `fetch_match_attempt_rescore_rows()` 增加 `--sku` / `skus` 篩選,可針對 DR.WU 這類明確 cohort 做 3-10 筆精準 materialize,不必為了 pilot 掃整批 `true_low_confidence`。
|
||||
- **V10.463 DR.WU / 達爾膚品牌 alias**: `marketplace_product_matcher` 補 `DR.WU / DR WU / DRWU / 達爾膚` 正規化,讓正式樣本中同規格玻尿酸保濕精華乳、杏仁酸亮白煥膚精華不再因品牌 token 不同被降成 brandless identity review;測試鎖住 exact / total_price / price_alert_exact。
|
||||
- **V10.462 PChome 補抓 UI 語意收斂**: Dashboard 補抓區塊標題、AI 中樞按鈕、前端 confirm 與 API 回覆全數改用「PChome 補抓產線 / 補抓未搜尋 / 未搜尋補抓」,避免「待比對」殘留在操作入口,和低信心待人工覆核混淆。
|
||||
- **V10.461 Dashboard 未搜尋語意修正**: 商品看板未進入 PChome 搜尋/補抓的品項不再顯示籠統「待比對」,改成「尚未搜尋」與「尚未進入 PChome 補抓」,避免操作員誤以為已有候選但尚未人工覆核;前端守門測試鎖住不得回退成舊文案。
|
||||
- **V10.460 ElephantAlpha 告警決策信封**: `resource_optimization` 會為資源壓力告警產生 deterministic `decision_envelope`,證據只來自 `action_plans`、CPU 實測與 hygiene 結果,Telegram 同時顯示決策信封、量測指標、判讀、系統處置與下一步;`ea_escalation` 模板也會渲染信封並使用 `decision_id` 作為 `momo:eig:*` callback,避免低信心升級告警只剩空泛文字或不可追蹤按鈕。
|
||||
- **V10.459 protected_existing_match 決策封包**: PChome 覆核信封開始解析 `existing_match_conflict`,把既有正式候選、新候選、雙方 matcher score 與 score delta 寫入 evidence / expected_impact / guardrails;新候選即使分數較高也維持 `can_auto_execute=false`,但 OpenClaw、PPT、Dashboard 與人工覆核可清楚看見該比較哪兩個候選。
|
||||
- **V10.458 OpenClaw / PPT 決策信封摘要**: 新增 `summarize_review_decision_envelopes()` 作為 PChome 覆核信封共用摘要 formatter;OpenClaw 週報/日報/月報、OpenClaw Bot competitor PPT data_summary 與 PPT KPI slide 都使用同一份 HITL / 資料品質 / action / trace 摘要,不再各自手寫 attempt_status 翻譯。
|
||||
- **V10.457 Dashboard / Excel 決策信封連動**: 商品看板 PChome 覆核卡顯示 `decision_envelope` 的決策等級、資料品質、HITL 與 trace;`/api/export/excel/pchome-review` 匯出新增決策信封 ID、建議代碼、責任人、資料品質、自動執行允許、阻擋原因與證據摘要,讓下載檔仍保留不可自動寫正式價差的 guardrails。
|
||||
- **V10.456 review queue 決策信封**: `fetch_competitor_review_queue()`、`fetch_competitor_review_queue_page()` 與 `/api/pchome-review/queue` 每筆 PChome 覆核候選都輸出 `decision_envelope`,包含標的 SKU/PChome 候選、match evidence、建議人工動作、預期價差、資料品質與「不可自動寫正式價差」guardrails;review queue cache key 升到 v3,避免正式環境沿用舊 payload。
|
||||
- **V10.455 EventRouter 決策信封直送**: 已帶 `decision_envelope` 的價格/覆核事件會略過 L1/L2 AI 重新摘要,直接用 Telegram 證據模板通知;決策信封新增標的區塊,顯示 SKU、商品名稱、PChome 候選 ID/名稱,避免 NemoTron 已有實證的價格告警被二次生成文字稀釋或產生額外模型呼叫。
|
||||
- **V10.455 rescore variant retraction CLI**: `audit_competitor_match_attempt_rescore.py --retract-variant-accepted` 可找出最新仍為 `rescore_accepted_current` 且帶 `variant_selection_review` 的 SKU,追加 `true_low_confidence` 退回列;保留歷史 audit trail,不刪資料、不寫正式價格表。
|
||||
- **V10.454 production rescore 入人工覆核隊列**: 以 latest-sku-only 口徑重算 745 筆 `true_low_confidence`,先追加 2 筆人工覆核列;V10.454 gate 補上 `variant_selection_review` 排除後,SKU `8884618` KATE 怪獸級持色唇膏(MOMO 多款任選 vs PChome 單一水光款)已退回最新 `true_low_confidence`,最終只保留 SKU `10922465` Herbacin 小甘菊護手霜 20ml 為 `rescore_accepted_current`。這次只寫 `competitor_match_attempts` 人工覆核列,未寫 `competitor_prices` / `competitor_price_history`,並已清除 Dashboard 與 competitor intel cache。
|
||||
- **V10.454 feeder / rescore 正式寫入閘門**: `CompetitorPriceFeeder` 現在只允許 `exact + total_price + price_alert_exact` 的 matcher 結果自動寫入 `competitor_prices`;`manual_review`、`identity_review`、`variant_selection_review`(例如 MOMO 多款任選唇膏對 PChome 單一水光款)會保留在 `true_low_confidence` 覆核,不得因分數剛過門檻而污染正式比價資料。Rescore audit 也會把 `variant_selection_review` 擋在 `accepted_current` 之外。
|
||||
- **V10.453 matcher 安全回收規則**: 新增 Herbacin 小甘菊護手霜 20ml brandless 同款 anchor;修正 `EX8` 型號不再被誤解析為 `x8` 入數;新增香氛固體凝膠 / 空氣芳香劑一側泛稱、一側明確香味或 No. 款式的 `aroma_scent_variant_conflict` veto。這輪目標是讓 retryable replay 可救回真同款,同時先封住 MIRAE 入數與 GONESH 香味款式的假陽性。
|
||||
- **V10.452 PChome rescore audit 最新狀態口徑**: `scripts/audit_competitor_match_attempt_rescore.py` 與 `fetch_match_attempt_rescore_rows()` 預設改成先取每個 SKU 最新 attempt,再套用 status / reason 篩選,與 Dashboard review queue 一致;需要回看歷史候選時才使用 `--include-historical-candidates`,避免舊低信心紀錄讓已修正、已否決或已入隊 SKU 重複回到操作報表。
|
||||
- **V10.451 low_score 操作分流拆分與 queue API**: Dashboard 比價覆核頁不再只給一個籠統低信心分頁;新增「近門檻可救」「證據不足」「低信心舊候選」三個篩選,`competitor_intel_repository.REVIEW_STATUS_FILTER_GROUPS` 同步提供對應分流,`/api/pchome-review/queue` 也能用同一套 `review_status` 做 read-only smoke / operator tools 查詢,讓 matcher 回刷、人工覆核、OpenClaw 報表能分清楚可自動回收、應保守等待、與需補搜尋的候選。
|
||||
- **V10.450 PChome 覆核 fast-count UI 語意與重算可採用指標**: 預設全量覆核頁跳過 exact count 時,模板會以「約」標記快取總數,避免操作員把快取總數誤認為即時計算;搜尋、分類與單一狀態分流仍保留精準總數。`fetch_competitor_coverage()` 同步輸出 `rescore_accepted_count`,Dashboard、daily/growth 與 OpenClaw 摘要會把「重算可採用待審」獨立顯示,不再只混在一般覆核隊列。
|
||||
- **V10.449 PChome 覆核 exact count 條件修正**: 只有預設「全部覆核、無搜尋、無分類」頁跳過 exact count;若使用搜尋詞、分類篩選或單一 review status,仍保留精準總數,避免操作員分頁資訊失準。
|
||||
- **V10.448 PChome 覆核全量頁跳過 exact count**: `review_status=all` 改用 shared overview cache 的待處理總數作為分頁總數提示,當頁只查 50 筆;單一狀態分流仍保留 exact count,避免每次操作全量覆核頁都為總筆數重掃整個 review queue。
|
||||
- **V10.447 PChome 覆核頁查詢方向反轉**: review queue page 改由最新 `competitor_match_attempts` 的可覆核狀態先縮小候選,再 join ACTIVE 商品與最新價,並用 `NOT EXISTS` 排除已有有效 `identity_v2` 正式 PChome 價格;避免「全部覆核」每次先掃全站 ACTIVE 商品後才過濾,提高核心比價覆核頁可操作性。
|
||||
- **V10.446 PChome 覆核頁 overview timeout 修正**: 覆核頁輕量 render path 的總覽改讀既有 shared dashboard cache / stale cache;若快取不存在,只用目前覆核頁資料補足 review count 與狀態分流,不再即時呼叫 `_load_competitor_decision_overview(session)` 的重型 SQL,避免正式資料量下 statement timeout。
|
||||
- **V10.445 PChome 覆核頁輕量渲染路徑**: `filter=pchome_review` 直接走覆核隊列專用 render path,不再先建完整 Dashboard `unique_items`;當頁只查 50 筆覆核 SKU 的商品資料、最新價、昨日價與週前價,仍沿用同一張新版表格、狀態分流、PChome 候選說明與人工覆核按鈕,降低核心比價覆核頁的全站資料負載。
|
||||
- **V10.444 PChome 覆核頁查詢瘦身**: `fetch_competitor_review_queue_page()` 將原本 count + page 兩次重跑 review CTE 改成單次 SQL 的 `total_rows` + `paged_rows` 查詢,同步取得總數與頁面資料,降低 `/?filter=pchome_review` 對 `latest_momo` / `latest_attempt` / `valid_competitor` 的重複掃描;正式站小批次 rescore 入隊後,用於維持核心比價覆核頁可操作速度。
|
||||
- **V10.443 PChome rescore 人工覆核入隊**: `scripts/audit_competitor_match_attempt_rescore.py --apply-accepted` 只會把最新版 matcher 已通過門檻的舊低信心候選追加為 `rescore_accepted_current` attempt,進入商品看板人工覆核隊列;不寫 `competitor_prices`、不寫 `competitor_price_history`,必須由操作員按「採用同款」後才正式更新 PChome 價差。Dashboard 補上「重算可採用待審」分流與狀態文案,避免安全回刷候選混在一般低信心項目裡。
|
||||
- **V10.442 CI/CD legacy GitLab 探測降噪**: `/cicd` 舊 GitLab pipeline API 預設改為 disabled,除非明確設定 `GITLAB_ENABLED=true` 並提供 `GITLAB_TOKEN`,否則不再打 `192.168.0.110:8929` 或 SSH fallback;正式 responsive smoke 造訪 `/cicd` 時只呈現可診斷空狀態,不再把已退役 GitLab endpoint 的 connection refused / permission denied 寫成錯誤噪音。
|
||||
- **V10.441 PChome matcher re-score audit**: 新增 read-only `competitor_match_attempt_rescore_audit` 服務與 `scripts/audit_competitor_match_attempt_rescore.py`,可針對既有 `competitor_match_attempts` 用最新版 matcher 重新分類成 `accepted_current` / `unit_comparable_current` / `identity_veto_current` / `low_score_current`,預設不寫 DB、不更新正式價格;商品看板同步補蘭蔻/達特醫/hoi/Saugella/Lactacyd 等 focused matcher reason 中文標籤。正式抽樣中 31 筆舊 `strong_exact_spec_match` 低信心資料,最新版 matcher 可讀出 10 筆 gate pass、1 筆單位價、11 筆 hard veto、9 筆仍低信心,作為後續人工覆核與批次回刷前的安全量化。
|
||||
- **V10.440 Mustela 爽身潤膚乳同款 anchor**: marketplace matcher 新增 `慕之幼爽身潤膚乳` identity anchor,並讓標題中插入「加量版」時仍可抽出同一身份詞;正式樣本 `【Mustela 慕之恬廊】慕之幼 加量版爽身潤膚乳 500mlX2入` vs `【慕之恬廊】慕之幼爽身潤膚乳(500毫升X2入)` 由 0.741 提升到 0.801,維持 `hard_veto=false`、人工 review 型態,不放寬全域門檻、不寫正式 `competitor_prices`。
|
||||
- **V10.439 外部 BI / 資料協作入口收斂**: `/metabase`、`/grist` 保持在 momo-pro 內部診斷 bridge,不再出現空白頁或錯連其他專案;`.env.example` 與 bi profile 的 Grist 預設 URL 改為 `https://mo.wooo.work/grist` / `GRIST_APP_HOME_URL`,測試同步守住 `grist.wooo.work` 與 `awoooi` 不再回到 app/template/env/compose 導覽設定。外部工具頁 H1 移除 viewport font scaling,改用新版 token 與手機 media query。
|
||||
- **V10.438 PPT QA 失敗重跑精準化**: `/observability/ppt_audit_history` 的 QA 失敗與 issue triage 卡片會從 PPT 檔名前綴推回原始 `report_type`,不再把所有視覺 QA 失敗硬編成 daily 重跑;單筆「重跑」會以 `force=true` 呼叫補齊 API,並在產生前只失效同一 `report_type + parameters` 的 active `ppt_reports` cache,避免重新產出仍命中舊簡報。頁面也把 audit lane 的預覽按鈕補上,讓失敗檔案可直接站內回放 PDF/PPTX 預覽。
|
||||
- **V10.437 業績圖表載入韌性與 QA 升級**: `analysis-chart-theme.js` 的 Chart.js loader 加入 4.5 秒 timeout 與 jsDelivr → unpkg → cdnjs 三來源 fallback;若外部 CDN 卡住或失敗,`/daily_sales`、`/growth_analysis` 會切到既有 HTML snapshot / fallback 圖表,不再留下空白圖表框。`check_sales_charts_runtime.js` 也從「canvas 有墨點」升級為檢查非零 dataset、可見元素、彩色資料筆跡與 canvas ink,避免只有座標軸的假通過。
|
||||
- **V10.436 daily_sales snapshot_date 型別修復**: `/daily_sales` 日期窗口查詢改為依 DB dialect 明確 cast:PostgreSQL 使用 `"snapshot_date"::date` 並把參數 `CAST(:start_date AS date)` / `CAST(:end_date AS date)`,SQLite 使用 `date("snapshot_date")`;metadata / fingerprint 查詢同步引用 cast 後日期,避免正式庫 `snapshot_date` 為 text 時出現 `text = date` / `text >= date` 類型錯誤。後台 `chart_generator_service.monthly_overview_chart()` 的月業績 SQL 也改為 `snapshot_date::date`,防止報表圖表因 text 欄位而空白。
|
||||
- **V10.435 商品列 PChome 狀態診斷翻譯**: Dashboard 商品列的 `_build_pchome_match_status()` 補上 `makeup_finish_conflict`、`nail_tool_function_conflict`、`schick_razor_line_conflict`、`variant_selection_review` 等具體狀態文案;`_load_pchome_match_attempt_map()` 同步解析 `match_diagnostic_json` 產生 `diagnostic_reasons` / `diagnostic_reason_text`,讓 overview、覆核隊列、商品列表與 Excel 的診斷語意一致。
|
||||
- **V10.434 PChome 人工覆核閉環補搜尋**: 商品看板 PChome review queue 新增「補搜尋」人工決策按鈕,對應 `needs_research` → `manual_needs_research`;`manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 納入全部覆核隊列與「人工閉環」篩選,避免操作員按完否決/單位價/補搜尋後項目從列表消失、後續無法追蹤。
|
||||
- **V10.433 PChome 覆核診斷標籤與 variant 回刷補強**: `competitor_intel_repository` 的 review queue / 商品看板 / Excel export 改為優先讀取 `match_diagnostic_json.reasons`,再 fallback 文字版 `error_message`;同步補 `makeup_finish_conflict`、`nail_tool_function_conflict`、`schick_razor_line_conflict`、`variant_descriptor_conflict` 等操作員可讀標籤,讓商品列表顯示「妝效質地不同、工具功能不同、除毛刀品線不同」而不是 raw machine code。matcher 另補 MUJI 精油芬香護手霜的 brandless exact recovery,PChome 標題缺品牌但身份詞與 50g 規格一致時可進 manual-review identity;peripera 多色任選 vs 單一色號會標記 `variant_selection_review` 並留在 `true_low_confidence`,避免被誤列為可批次救回。
|
||||
- **V10.432 近門檻比價 hard-veto 補強**: marketplace matcher 不放寬 `MIN_MATCH_SCORE`,針對正式 `true_low_confidence` 前段新增窄範圍防錯配:M.A.C `MACximal` 柔霧唇膏 vs 緞光唇膏標記 `makeup_finish_conflict`、ERBE 指甲清垢棒 vs 指甲緣刨刀標記 `nail_tool_function_conflict`、Schick 舒芙 vs 舒綺仕女除毛刀標記 `schick_razor_line_conflict`,三者皆進 hard veto;同時把 `潤膚乳` / `身體乳` / `嬰兒乳液` / `寶寶乳液` 納入乳液型別,讓慕之幼爽身潤膚乳等真同款回刷更穩定。新增測試鎖住 MUJI 護手霜、Mustela 慕之幼潤膚乳、Herbacin 小甘菊護手霜可 exact,並確保高 variant 錯配不被 focused rule 推進。
|
||||
- **V10.431 Telegram callback byte-safe**: `triaged_alert()` 的 `momo:eig:*` HITL callback 改為依 UTF-8 byte 長度截斷,不再用字元數截斷;中文或過長 `event.id` / `decision_envelope.decision_id` 仍會保留可追蹤 payload,且保證 `callback_data` 不超過 Telegram 64-byte 限制,避免專業排版告警因 callback 太長而整則送出失敗。
|
||||
- **V10.430 NemoTron 決策 callback 追蹤 ID 修補**: `NemoTronDispatcher._send_telegram()` 會把 `decision_envelope.decision_id` 提升為 EventRouter `event.id`;`triaged_alert()` 也會在上游缺 `event.id` 時改用 `decision_id` 產生 `momo:eig:*` callback,避免價格決策通知的「忽略此事件」audit 落成 `unknown` 而無法追查。
|
||||
- **V10.429 111 / NemoTron 治理回歸補齊**: 補齊 `.env.example` 中 111 circuit breaker、111 allowlist proxy、部署 smoke、資料庫與 Redis runtime keys,並同步大檔 inventory 行數,讓完整測試可覆蓋最新 `V10.425`–`V10.428` 變更;此版不放寬商品比對門檻、不修改 `competitor_prices` 寫入規則。
|
||||
- **V10.428 NemoTron 價格決策信封落地**: `NemoTronDispatcher` 的 `price_alert` 與 `human_review` 事件現在會產生 12 Agent 共用 `decision_envelope`,把同款證據、價差、七日銷量變化、營收流失、建議行動、HITL guardrails、資料品質與 trace 同步寫入 EventRouter event 與 KM metadata;這讓 Telegram、AI 觀測台、PPT QA 與後續 Agent 協作能讀同一份可稽核證據,而不是各自解析告警文字。
|
||||
- **V10.427 111 fallback circuit breaker**: `OllamaService` 在選到 111 final fallback 前先讀 `ai_calls` 近 60 分鐘比例;若 Ollama 呼叫 >=20、111 >=5 且占比 >=5%,會短暫跳過 111 並清除 resolved host cache,避免 111 在已偏高時繼續承接長任務。DB 觀測失敗採 fail-open,避免觀測層故障反向中斷 GCP-A/GCP-B 正常路由。
|
||||
- **V10.426 111 proxy 拒絕日誌去重**: `ollama111_allow_proxy.py` 對同一來源 IP 的 reject log 預設 60 秒去重,保留 110 / 121 被擋的可觀測性,同時避免旁路 VM 持續探測時把 111 的 proxy log 與磁碟 I/O 刷高。
|
||||
- **V10.425 111 fallback 使用率護欄**: Scheduler 每 15 分鐘只讀 `ai_calls` 檢查 111 Ollama fallback 使用率,預設 60 分鐘內 Ollama 呼叫 >=20、111 呼叫 >=3 且占比 >=5% 才推 Telegram,並列出 111 caller Top 5;此護欄只觀測與告警,不改路由、不寫 DB、不重啟服務,讓 111 被異常承接高負載時可即早發現。
|
||||
- **V10.424 111 proxy LaunchAgent 安裝路徑穩定化**: `install_ollama111_allow_proxy.sh` 會把 proxy script 複製到 `~/.local/share/momo-pro-system/ollama111_allow_proxy.py` 後再寫入 LaunchAgent,避免 111 重啟或 iCloud repo 路徑未掛載時代理失效;同時清空舊 stderr log,讓安裝後狀態更容易判讀。
|
||||
- **V10.423 12 Agent 決策信封**: `triaged_alert()` 支援 `decision_envelope` 結構化區塊,讓 Hermes / NemoTron / OpenClaw / ElephantAlpha 與後續 12 角色決策統一輸出 `severity`、`evidence`、`recommended_action`、`expected_impact`、`confidence`、`guardrails` 與 `trace`;缺證據時必須明確標記資料品質與 HITL 邊界,避免再出現空泛效益預測或不可追溯告警。
|
||||
- **V10.422 111 proxy LaunchAgent 持久化**: 新增 `scripts/ops/install_ollama111_allow_proxy.sh`,在 111 以 user LaunchAgent 安裝 `com.momo.ollama111-allow-proxy`,啟動時設定 `OLLAMA_HOST=127.0.0.1:11434`、重啟 Ollama、載入 allowlist proxy,避免重開機或重新登入後 111 又回到 LAN 全開狀態。
|
||||
- **V10.421 Kanebo Milano / hoi 蠟燭品類防錯配**: marketplace matcher 追加 `kanebo_milano_type_conflict` 與 `hoi_candle_line_conflict`,將 Kanebo Milano Collection 蜜粉餅 vs 絕色香水、hoi 日京山風香氛蠟燭 vs hoi!LAB 實驗室香氛蠟燭經典篇列為 hard veto;同品牌、同系列字樣或同容量仍不可跨品類/跨產品線直接比價。
|
||||
- **V10.420 111 Ollama LAN allowlist proxy**: 追查 111 高負載時確認來源不是 momo-pro,而是 110 上 `awoooi-cd` 臨時測試與 121 VMware VM 直接打 `192.168.0.111:11434`,繞過 `ai_calls` 與 momo-pro router 載入 7B runner。新增 `scripts/ops/ollama111_allow_proxy.py`,將真實 Ollama 收斂到 `127.0.0.1:11434`,由 user-space proxy 綁 `192.168.0.111:11434` 並預設只允許 111 本機與 188 生產宿主;110 / 121 會被 reset,111 fallback 保留給 momo production。
|
||||
- **V10.419 Dr.Hsieh LabSmart 精華品線防錯配**: marketplace matcher 追加 `dr_hsieh_labsmart_line_conflict`,只針對 Dr.Hsieh/達特醫的 `LabSmart Hi-Tech` / `LabSmart Classic` 精華被拿去對 `神經醯胺多重修復保濕精華液` 的近門檻錯配做 hard veto;同品牌同容量但不同產品線不再因規格相同停在 `true_low_confidence` 或被誤推進比價。
|
||||
- **V10.419 production pilot**: 正式回刷 SKU `10413050` / `10413051`,兩筆 Dr.Hsieh LabSmart 精華 vs 神經醯胺多重修復精華皆由 `true_low_confidence` 轉為 `identity_veto`,`diagnostic_codes=["dr_hsieh_labsmart_line_conflict"]`;`matched` 維持 1619、`true_low_confidence` 753→751、`identity_veto` 4011→4013,無正式 `competitor_prices` 覆寫。
|
||||
- **V10.418 bge-m3 一致性檢查不打 111**: `verify_embedding_consistency()` 預設只比對 GCP-A / GCP-B,不再每週把 111 Mac 納入 bge-m3 背景驗證;新增 `EMBED_CONSISTENCY_INCLUDE_111=false` 預設,只有救急需要驗證 fallback 模型時才 opt-in。這補上 V10.417 後仍會由監測任務載入 111/GCP-B embedding runner 的缺口。
|
||||
- **V10.417 Embedding/RAG 背景負載保護**: `OllamaService.generate_embedding()` 新增 `allow_111_fallback`、timeout cap、輸入長度 cap 與 `/api/embed keep_alive=1m`;OpenClaw learning worker 與 RAG 查詢預設只跑 GCP-A → GCP-B,不再把 `bge-m3` 背景 embedding / semantic RAG 轉嫁到 111。預設 `OLLAMA_EMBED_TIMEOUT=15`、`OLLAMA_EMBED_MAX_TIMEOUT=15`、`OLLAMA_EMBED_MAX_CHARS=4000`,避免 embedding worker 在 GCP-B/111 長時間常駐或拖住 runner。
|
||||
- **V10.416 私密清潔 / 彩妝用途 / 棉棒 / 蘭蔻品線防錯配**: marketplace matcher 追加窄範圍 hard-veto guard,讓 SAUGELLA 日用/加強 vs 黃金女郎型、Lactacyd 清新舒涼 vs 生理呵護、LUNASOL 頰彩 vs 眼彩、MUJI 細軸棉棒 vs 黑色棉棒、LANCOME 超極光晶露 vs 超極限肌因精華露不再停留在模糊 `true_low_confidence`,而是以 `*_variant_conflict` / `makeup_usage_conflict` / `lancome_line_conflict` 明確拒絕;不調整 `MIN_MATCH_SCORE`,也不放寬真同款進 matched 的門檻。
|
||||
- **V10.416 production pilot**: 正式回刷 7 筆近門檻錯配樣本,SAUGELLA 2 筆、LUNASOL 頰彩 vs 眼彩、LANCOME 超極光 vs 超極限、我的心機兒童防曬 vs 海洋友善防曬、Lactacyd 清新舒涼 vs 生理呵護、MUJI 細軸棉棒 vs 黑色棉棒皆更新為 `identity_veto`;`matched` 維持 1619、`true_low_confidence` 759→753、`recoverable_low_score` 1→0、`identity_veto` 4004→4011,無正式 `competitor_prices` 覆寫。
|
||||
- **V10.415 Hermes 預設不落 111 + 比對保護**: `OllamaService.generate()` 新增 `allow_111_fallback` 參數,預設維持三主機相容;Hermes intent / competitor analyst 改以 `HERMES_ALLOW_111_FALLBACK=false` 預設只跑 GCP-A → GCP-B,兩台都不可用時交給規則引擎或 DB 證據 fallback,不再把批量價格分析與意圖分類轉嫁到 111。同版 marketplace matcher 將防曬類列入 variant-sensitive,排除 SPF/PA/UVA/UVB 這類規格 token 被誤當型號,避免「兒童防曬乳」與「海洋友善保濕防曬乳」誤配;Recipe Box 兒童防曬氣墊粉餅保留精準同品線例外;另新增 `pack_quantity_difference`,讓 Beauty Foot 足膜 5入 vs 4入走 unit comparable,不再卡在低信心。
|
||||
- **V10.415 production pilot**: 上線後以 SKU `12670442` 單筆回刷驗證 Beauty Foot 足膜 5入 vs 4入:最新 attempt 由 `true_low_confidence` 轉為 `refresh_unit_comparable`,`diagnostic_codes` 補上 `pack_quantity_difference` / `unit_comparable`,`matched` 不增加、正式 `competitor_prices` 不覆寫;整體最新分布由 `true_low_confidence=760, refresh_unit_comparable=64` 變為 `true_low_confidence=759, refresh_unit_comparable=65`,符合「可單位價覆核但不可直接當同款總價告警」邊界。
|
||||
- **V10.414 MCP fetch run readiness gate**: 新增 `mcp_fetch_run_readiness` read-only builder、GET/POST endpoint、UI run readiness 審核面板與 deployment readiness smoke target,在 run package 後檢查 command preview、receipt path、artifact path、節流/timeout/dry-run-first 與操作員 shell-only 邊界;API/UI 不執行 CLI、不抓外站、不寫檔、不開 DB、不掛 scheduler,只放行到人工 shell dry-run 與後續 receipt gate。
|
||||
- **V10.413 Code Review 預設保護 111 fallback**: production `ai_calls` 顯示 GCP-A 不可達時,Code Review OpenClaw 會先耗掉 primary timeout,再讓 GCP-B 撐到 60s,最後落到 111 `llama3.2` 成功,造成 111 與 GCP-B 高負載。新增 `CODE_REVIEW_ALLOW_111_FALLBACK=false` 預設:Code Review 的 Hermes LLM scan / OpenClaw assessment 只跑 GCP-A → GCP-B;只有明確設 true 才把部署後重分析丟給 111。若 GCP-A/GCP-B 都失敗且 Claude/Gemini 未顯式開啟,改回 deterministic 本地降級摘要,不呼叫 Gemini,也不再用 111 承接非即時重分析。
|
||||
- **V10.412 MCP fetch run package gate**: 新增 `mcp_fetch_run_package` read-only builder、獨立 route extension、GET/POST endpoint、UI run package 審核面板與 deployment readiness smoke target,將已通過的 target review 轉成操作員可覆核的 command argv preview 與 receipt path 契約;API/UI 不執行 CLI、不抓外站、不寫檔、不開 DB、不掛 scheduler,只放行到後續 run readiness review。
|
||||
- **V10.411 rom&nd / Summer’s Eve / Solone 近門檻 review-only 回收**: marketplace matcher 追加三條窄範圍 focused identity:rom&nd 果汁唇釉 2.0 catalog、Summer’s Eve 舒摩兒全肌防護浴潔露 2入、Solone 持久眼線筆;皆只進 `identity_review` / manual-review,不直接價格告警。production pilot 已回刷 3/3,`matched` 1616→1619、`true_low_confidence` 763→760;rom&nd 染眉膏 ZO&FRIENDS 色號、Summer’s Eve 雙天王任選、Lactacyd 清新舒涼 vs 生理呵護、MAC 柔霧 vs 緞光、NIVEA / 曼秀雷敦包數差異仍不自動救回,維持準確率優先。
|
||||
- **V10.410 Code Review timeout 梯度改為保護 111**: 部署後實測顯示 GCP-A 從 188 失聯時,Code Review 仍會先等 primary 45s,GCP-B 完整審查 prompt 又常因 25s 太短而 timeout,最後轉落 111。`CODE_REVIEW_OLLAMA_TIMEOUT` 預設收斂為 `15s`,`CODE_REVIEW_OLLAMA_SECONDARY_TIMEOUT` 放寬為 `60s`;Hermes LLM scan 若啟用則 primary `15s`、secondary `45s`。目標是 A 掛時更快讓位給 B,並給 B 足夠時間完成,避免過早壓到 111。
|
||||
- **V10.409 MCP fetch target review gate**: 新增 `mcp_fetch_target_review` read-only builder、GET/POST endpoint、UI target review 審核面板與 deployment readiness smoke target,讓 manual fetch handoff 通過後,先審核 adapter registry 公開入口、每平台節流、樣本數、timeout 與 rollback plan;API/UI 不保存 payload、不發外部 request、不開 DB、不寫入、不掛 scheduler,也不會自動打開 manual fetch。
|
||||
- **V10.408 OPI 指甲油 catalog review-only 回收**: marketplace matcher 針對 OPI 類光繚指甲油加入同系列 catalog focused identity,只在「白日夢遊」或「驕傲果凍」系列名雙方一致時進 `identity_review`;12色/11色視為可選色號數差異,不當作販售件數,跨系列仍維持 suppress。production pilot 已回刷 KATE 怪獸級持色唇膏限量款與兩個 OPI SKU 共 3/3,`matched` 1613→1616、`true_low_confidence` 766→763,三筆皆為 `alert_tier_identity_review` / `price_basis_manual_review`,不進 Hermes 直接價格告警。
|
||||
- **V10.407 Hermes/OpenClaw runner 熱駐留收斂**: V10.406 後續觀測顯示 GCP-B 仍會被 Hermes/OpenClaw 的 `24h` keep-alive runner 壓高 load,導致 GCP-B generate timeout 後轉落 111。`HERMES_KEEP_ALIVE` 與 `OPENCLAW_STRATEGY_OLLAMA_KEEP_ALIVE` 改為 env 可控且預設 `5m`,並補測試禁止 OpenClaw strategy 再硬寫 `keep_alive="24h"`;GCP-A 目前從 188 仍逾時,需另行修復主機/防火牆。
|
||||
- **V10.406 Code Review Ollama keep-alive 收斂**: production audit 顯示 Gemini 24h 已為 0,但 GCP-A `34.143.170.20:11434` 從 188 逾時、GCP-B 曾因多個 Ollama runner 長駐造成 generate timeout,導致部署後 Code Review 轉落 111。`CODE_REVIEW_OLLAMA_KEEP_ALIVE` 預設由 `24h` 改為 `5m`,讓 GCP-B/111 的 code review runner 不再長時間常駐;實測已重啟 GCP-B Ollama 並確認 `gemma3:4b` 可於約 6.2s 完成短生成。
|
||||
- **V10.405 MCP manual fetch handoff gate**: 新增 `mcp_manual_fetch_handoff` read-only builder、GET/POST endpoint、UI handoff package 審核面板與 deployment readiness smoke target,讓 runtime promotion package 搭配操作員公開頁面、節流、無登入/反爬、無 DB、無 scheduler 確認後,只放行到人工 fetch gate operator review;API/UI 不保存 payload、不打 health、不開 DB、不抓外站、不掛 scheduler,也不會自動打開 manual fetch。
|
||||
- **V10.404 Hermes 競價威脅漏斗只吃 direct alert**: `HermesAnalystService.fetch_candidates()` 的 `competitor_prices` JOIN 新增硬條件,只讓 `match_type=exact`、`price_basis=total_price`、`alert_tier=price_alert_exact` 的 identity_v2 配對進入 Hermes 競價威脅分析;`identity_review`、`unit_price_review` 與 `suppress` 仍保留在 dashboard / 人工覆核資料流,但不再消耗 Hermes token 或被上游視為直接價格威脅候選。production fresh 配對分布顯示直接告警約 497 筆、覆核型約 216 筆,本版將兩者在 AI 威脅入口切開。同版 matcher 追加 KATE 怪獸級持色唇膏、植村秀武士刀眉筆筆蕊、The Forest 焦糖楓葉擴香禮盒等近門檻 review-only 回收線,仍只進人工覆核,不直接價格告警。
|
||||
- **V10.403 近門檻候選重驗擴池**: production 最新 72h attempt audit 顯示 7853 筆近門檻/被擋候選中,有 105 筆可被現行 matcher 回收,其中 46 筆已達 `price_alert_exact`、59 筆只進 `identity_review`。`CompetitorPriceFeeder._fetch_retryable_candidate_skus()` 擴大每日 revalidation 候選池,納入 `true_low_confidence`、`unit_comparable`、`refresh_unit_comparable` 與高分 `identity_veto`;但仍要求 PChome product_id、`best_match_score >= 0.70`,且正式寫入前必須由現行 matcher 重判、通過 hard-veto 與既有配對保護。
|
||||
- **V10.402 catalog variant identity-review 回收**: marketplace matcher 新增 Johnson’s 嬰兒潤膚乳 catalog 對同香型、IM MEME 涼感定妝噴霧、SO NATURAL FIXX 定妝噴霧三條 review-only focused identity,讓同品線 catalog / variant 候選可脫離 `true_low_confidence` 但只進 `identity_review`,不直接價格告警;同版明確保留 MUJI 品牌缺漏護手霜等 brandless catalog 案例在低信心,避免品牌缺漏與多款任選同時存在時被過度救回。
|
||||
- **V10.401 focused identity 邊界整理**: 將 Laundrin TOKYO 車用夾式消臭芳香劑判定抽成 `_has_laundrin_tokyo_car_freshener_alignment()`,並在 variant descriptor guard 中豁免同一條強身份線,避免 TOKYO / 車用 / 芳香劑語序差異造成誤擋。Yuskin 經典乳霜 30g 6 入組改用 `_has_exact_count_alignment()` 判斷包數,保留 6入 vs 6盒這類同數量但不同容器字詞的人工覆核路徑。
|
||||
- **V10.400 氣墊粉餅補充蕊包數正規化**: marketplace matcher 針對氣墊粉餅新增保守的 `cushion_refill_pack_alignment`,只在一側明確為 `一盒兩蕊 / 2蕊`、另一側為單規格 `15g x2` 這類乘數包裝時,解除 `multi_component_conflict` 並進 `identity_review`。CLIO 羽緻無限緞光氣墊粉餅「一盒兩蕊」可被同款覆核;`2盒4蕊` 對 `15g x2` 仍維持 hard veto。同版補香氛蠟燭、TOKYO 車用消臭芳香劑、融蠟燈與有機護膚油 search identity anchors。離線 audit 759 筆 accepted 從 753 提升到 754,剩餘 5 筆 fresh veto 皆為應擋的套組/品類差異。
|
||||
- **V10.399 多香味 catalog 價格告警降級**: marketplace matcher 新增 `variant_selection_review`,當一側是無明確選項的商品線、另一側列出多個具名香味/款式選項時,允許同款身份回收但只進 `identity_review`,不直接進 `price_alert_exact`。首個正式案例是 HH 女性私密衣物抗菌手洗精 200ml 對 PChome 白麝香/清新花園/寶貝粉香多香味 listing;此規則避免把多香味 catalog 價格誤當單一 variant 精準比價。
|
||||
- **V10.398 true low confidence 保守回收**: marketplace matcher 針對正式前段 `true_low_confidence` 補一輪 focused exact identity lines,讓 Baan 嬰兒修護唇膏、植村秀 3D 極細防水眼線膠筆、YSL 恆久完美透膚煙染腮紅、HH 私密植萃美白緊緻凝露、Lab52 學習刷牙漱口水、Benefit 經典菲菲染唇液、Herb24 晨霧純精油擴香儀、Pavaruni 40 香味 10ml 精油與 GATSBY 爆水擦澡濕巾等近門檻真同款可被回收;未放寬 `MIN_MATCH_SCORE`。同版保留 peripera 多色任選對單一色號、LUNASOL 頰彩對眼彩組、MUJI 細軸棉棒對黑色棉棒的低信心保護,並讓多組件套組即使達強身份證據也停在 `identity_review`,避免總價被誤當精準價格告警。
|
||||
|
||||
### 2026-05-21:瀏覽器測試守門與 PChome 熱路徑優化
|
||||
- **V10.397 離線 audit false negative 收斂**: marketplace matcher 針對 audit 剩餘 fresh bad 補三個保守修正:`200ml+200ml` 這類括號內規格串不再被計成額外組件;Kiehl’s `1號護唇膏` 的 `1` 視為商品線名稱而非色號;P.SHINE BEAUTY FOOT 雙面足部去硬皮磨砂棒以品牌 + 足部硬皮磨砂棒語意進 `identity_review`。同批補 focused exact identity lines,讓 Biodance、SAB、LUSH、Kanebo、ARTMIS、Nailmatic、小浪、YUNMI、AQUIESSE、資生堂等低分但強證據同款被正確拉回;高證據 exact 才可進 `price_alert_exact`,證據不足者仍進 `identity_review`。離線 audit fresh bad 從 9 降到 6,剩餘皆為多組件/套組差異。
|
||||
- **V10.396 多選 catalog 對 generic count 組合放行**: marketplace matcher 對「多款任選 catalog listing」對上同數量 generic `N入組` 候選新增保守豁免:需品牌、品類、基礎規格與數量一致,且 generic 端沒有具名色款/香味選項,才不觸發 `variant_option_conflict`。John’s Blend 香氛擴香罐 85g 任選 3 入 vs PChome 3入組會進 `identity_review`,不直接價格告警。
|
||||
- **V10.395 離線競品身份 audit 工具**: 新增 `scripts/audit_competitor_identity_jsonl.py`,可把 production DB 匯出的 competitor identity JSONL 在本機重跑 current matcher,輸出 accepted / veto / low-score / fresh bad 摘要與樣本;工具不連 DB、不寫 DB,用來取代在 188 app container 內全量重掃造成的 memory 壓力。
|
||||
- **V10.394 多色 catalog / 入門組防錯配**: marketplace matcher 補「琥珀橙 / 干邑棕 / 賽車綠」等車用香氛色款詞,當 MOMO 是多色/多款 catalog listing、PChome 是單一色款候選時會保留 `variant_option_conflict` hard veto;同時把 `入門組` 納入套組詞,避免理膚寶水抗敏入門組被拿去跟單瓶乳液做總價比價。
|
||||
- **V10.393 組合包 `+` 判定修正與 catalog 補強**: marketplace matcher 的組合包件數判定會先排除 `SPF50+`、`PA++++` 等防曬係數加號,以及 `NTT80+AL414` 這類純型號碼串,避免把防曬品與 OPI 套組的規格/型號加號誤判成多一個商品組件;CeraVe 三件組 vs 兩件組仍維持 `multi_component_count_conflict` hard veto。同版收緊品牌 alias 判定,避免只有品牌名就觸發商品線加成,並補 Baan 貝恩嬰兒修護唇膏「原味/草莓」catalog listing 放行。
|
||||
- **V10.392 組合包件數防錯配**: marketplace matcher 新增 `multi_component_count_conflict`,當 MOMO 與 PChome 都是 `+`/`+` 組合包但組件數不同時直接進 `not_comparable`,避免三件組被拿去跟兩件組做總價告警;同步把該原因加入 evidence flags,讓告警與審核畫面可以清楚顯示「組合包件數不同」。
|
||||
- **V10.391 多款任選 catalog listing 防錯配**: marketplace matcher 新增 `catalog_variant_listing_alignment`,當 MOMO/PChome 雙方都是多款/多色/多香味任選 listing,且商品線、規格與類型一致時,可放行香氛擴香罐、香氛蠟燭等 catalog 型同款;同時把 Relove 菸鹼醯胺 vs 胺基酸私密清潔凝露列為變體衝突,並讓 competitor feeder 不再只因 `strong_exact_spec_match` 就把低分候選視為 recoverable,避免只同規格但品線不同的商品回寫正式比價。
|
||||
- **V10.390 PChome 近門檻商品比對規則**: marketplace matcher 補 17 組近門檻真同款召回與錯配防線,包含 OBgE 防曬棒、ARTMIS 私密清潔慕斯、Seche Vite 快乾亮油、TAICEND 屁屁噴、femfresh / VIGILL 私密清潔、Solone 眼部飾底乳、HYDSTO 車載香薰、小米 S101 刮鬍刀、PRAMY 定妝噴霧、I'M MEME 修容打亮棒、檜山坊滾珠精油、ARM&HAMMER 體香膏、Brush Baby WildOnes 電動牙刷與 Palmer's 按摩乳;同時把香氛/私密慕斯/定妝噴霧 finish 差異列為 variant-sensitive,避免不同香味、蔓越莓 vs 金縷梅、柔焦霧面 vs 水光亮面被誤推成直接價格告警。
|
||||
- **V10.388 精華乳 / 精華霜變體防錯配**: marketplace matcher 新增精華類 formulation conflict guard,當共享 identity anchor 只到「精華」但一側是「精華乳」、另一側是「精華霜 / 精華液」時會標記 `variant_descriptor_conflict` 並壓低同款分數,避免自白肌等同品牌相近品線被錯推成 PChome/MOMO 可直接價格告警。Competitor feeder 同步會用最新 matcher 重新驗舊配對;若舊 `identity_v2` 已被現行 matcher 判成低分或 veto,允許新的高信心候選替換,避免歷史錯配卡住正式 `competitor_prices`。
|
||||
- **V10.387 EA 比價 HITL 告警證據排版**: Elephant Alpha 的 DB evidence 與 Hermes pre-fetch action 現在會把 PChome/MOMO 同款證據帶進 Telegram:`match_type`、`price_basis`、`alert_tier` 與 `match_score` 會獨立成「證據」行,讓人工審核能分辨高信心同款、總價可比、單位價覆核與身份覆核,不再只看到乾巴巴的 `MOMO vs PChome` 長句。同版 marketplace matcher 補 Relove「私密潔淨凝露」identity anchor 與聯名款搜尋噪音,避免 PLAY BOY / 小虎等活動詞壓過真同款名稱。
|
||||
- **V10.386 Gemini compose hard default / KATE 唇膏比對**: `docker-compose.yml` 針對 `momo-app`、`scheduler`、`telegram-bot` 明確釘住 `GEMINI_API_HARD_DISABLED=true` 與 `GEMINI_FALLBACK_ENABLED=false` 的預設,讓 `.env` 保留 API key 時也不會自動產生 Gemini 付費出站;AI SOT 與 compose 測試同步鎖定此契約。同版 marketplace matcher 補 KATE/凱婷「柔霧裸唇膏」identity anchor,避免 MOMO 長標含東京夜喫茶系列與任選文案時漏掉 PChome 同款短標。
|
||||
- **V10.385 Lactacyd / MAQuillAGE 櫃別同款比對**: Marketplace matcher 補強 Lactacyd 私密潔浴露多款任選長標與 PChome 短標同款放行,並橋接「資生堂東京櫃」與 `MAQuillAGE 心機彩妝` 在「心機星魅蜜光圈潤唇膏」上的櫃別/品牌 alias,避免真同款被 `brand_conflict` 擋掉。
|
||||
- **V10.384 Karadium 無規格眼影棒同款放行**: Marketplace matcher 對 Karadium「閃亮珍珠眼影棒」新增品牌 + 強 identity anchor 加分,當 PChome 標題省略 1.4g 規格但品名/品牌高度一致、無變體衝突與 hard veto 時仍可進入 exact identity 告警候選,避免同款因規格缺字漏報。
|
||||
- **V10.383 EA JSON fallback / EDM cache 自癒 / 比對別名補強**: Elephant Alpha 協調器現在可容忍 fenced JSON 與混文字 JSON;若仍無法解析,會改用 DB/Hermes 實證產生保守人工覆核決策,不再輸出舊式 OpenClaw 策略 plan 或自動調價暗示。EDM promo dashboard shared cache 遇到損毀 pickle 會自動刪除並重建,避免每個 worker 重複噴 `UnicodeDecodeError`。Marketplace matcher 補上 Curel/珂潤、Karadium 與兩個強 identity anchor 測試,降低真同款漏報。
|
||||
- **V10.382 唇膏 exact identity 寬價差豁免**: marketplace matcher 對「同品牌 + 共享唇膏 identity anchor + 規格完全一致 + 無色號/變體衝突」的唇膏類商品,允許 sequence score 略低時仍套用 `price_penalty_suppressed_wide_exact_identity`;這只處理 PChome/MOMO 標題順序與行銷字差異造成的真同款漏報,不放寬顯性色號不同的 hard veto。
|
||||
- **V10.381 browse.sh 比價診斷計畫**: PChome feeder 在 `no_result`、`no_match`、低信心、單位價覆核、既有配對保護與爬蟲錯誤時,會把 read-only `browse_diagnostic_json` 寫入 `competitor_match_attempts`,內含 PChome search URL 與建議 `browse get/open` 命令;正式排程仍 API-first,`PCHOME_FEEDER_BROWSE_SH_EXECUTE_ENABLED=false` 預設不自動開瀏覽器,避免瀏覽器彈窗、登入或密碼提示干擾。
|
||||
- **V10.380 111 Ollama final fallback 收斂**: 111 Mac fallback 從救急路徑改成更短的保護路徑,`OLLAMA_111_MAX_TIMEOUT` 預設由 45s 收緊到 20s,並新增 `OLLAMA_111_NUM_PREDICT=512` 輸出上限;落到 111 時仍會降級重模型到 `llama3.2:latest`、縮 `num_ctx=4096`、`keep_alive=5m`,避免 GCP-A/GCP-B 短暫 timeout 後把長篇 Hermes/OpenClaw 工作轉嫁到 111 造成 swap 與 load 飆高。
|
||||
- **V10.379 MCP runtime promotion gate**: 新增 `mcp_runtime_promotion` read-only builder、GET/POST endpoint、UI promotion package 審核面板與 deployment readiness smoke target,將 MCP activation evidence 與 runtime smoke receipt 合併審核,讓 completion audit 的 runtime 缺口可由人工收據明確補齊。
|
||||
- **V10.379 只讀安全邊界**: 本階段不保存 payload、不打 health、不開 DB、不抓外站、不掛 scheduler,也不會因 promotion 通過自動打開人工 fetch gate;正式 fetch / DB write / scheduler attach 仍需各自獨立 gate。
|
||||
- **V10.378 AI 推薦頁首屏 Gemini 防漏**: `/ai_recommend` 首屏狀態快照新增 provider sanitization,即使舊 cache / env 內出現 `default_provider='gemini'` 或 `recommended_provider='gemini'`,也會回到 `ollama`,避免 UI 把 Gemini 顯示成主推薦路徑;`/api/ai/set_provider` 同步正規化 provider 輸入,保留 Gemini 只能作 Ollama 失敗備援的拒絕訊息。
|
||||
- **V10.377 Gemini 主路徑防漏補強**: `AIProviderService._get_recommended_provider()` 不再於 Ollama 不通時推薦 `gemini` 作為主提供者;`llm_model_router` 的 `ea_engine` 即使 caller 傳入 `gemini-2.0-flash` default,也會改回 `hermes3:latest`,需要深推理才升 `deepseek-r1:14b`;`ElephantAlphaOrchestrator` 的 OpenClaw registry / system prompt 改為 Ollama-first,避免 L3 HITL prompt 繼續把 Gemini 當主模型描述。同步補 AI SOT 與防回歸測試。
|
||||
- **V10.376 Recipe Box 同款防曬漂移比對**: `services/marketplace_product_matcher.py` 對 Recipe Box 多效提亮防曬霜新增 shared identity anchor 加分,當 MOMO 長標含兒童/無毒/天然彩妝等行銷詞、PChome 以「韓兔 多效提亮防曬霜」呈現時仍可判定同款;同步測試鎖住 `shared_identity_anchor_recipe_box_line`,避免平台名稱漂移讓同款價格告警漏報。
|
||||
- **V10.375 過期活動爬蟲排程 opt-in**: `run_scheduler.py` 將固定 LPN 的 `edm_task` / `festival_task` 改為 `MOMO_ENABLE_LEGACY_EDM_SCHEDULE=true` 才註冊,季節活動 `mothers_day_2026` / `valentine_520_2026` / `labor_day_2026` 改為 `MOMO_ENABLE_SEASONAL_PROMO_SCHEDULE=true` 才註冊;`services/data/crawler_config.json` 同步暫停已失效的 mothers_day LPN,避免 scheduler 定時打過期 MOMO 活動頁造成 Selenium browser loop 與無效負載。手動 API / CLI 指定 LPN 仍保留;同版整合 NIVEA/OPI 等比價搜尋 noise 與 identity anchor 補強。
|
||||
- **V10.374 EDM 失效頁告警止血**: `scheduler.py` 新增 MOMO EDM alert guard,`run_edm_task` / `run_festival_task` / `run_promo_event_task` 遇到「很抱歉此EDM不存在」時會接受 browser alert、寫入 `Skipped / edm_unavailable` stats,且不再送 EventRouter failure,避免 festival / mothers_day 過期活動頁重新累積 Telegram queue;同版整合 REJURAN 麗駐蘭唇膏同款在價格比過寬時的 exact-identity 價格懲罰豁免。
|
||||
- **V10.373 PChome 同款名稱漂移整合**: 整合並修正 concurrent matcher work,新增 MAC/M.A.C 品牌 alias、Yuskin 經典乳霜 4入/4盒組同數量 bundle equivalent、AHC 瞬效 B5 玻尿酸關鍵字重排 anchor;修復 `_count_score()` 縮排破壞與 unreachable code,讓新增測試可穩定通過。
|
||||
- **V10.372 Smoke 與 EventRouter queue 修復**: 修正 AI automation smoke 對 NemoTron fallback 的 class 判斷,改接受實際存在的 `NemotronDispatcher._hermes_rule_fallback`,避免 Hermes fallback 正常卻被誤報 critical;EventRouter 失敗佇列回放改為重建短版 HTML-safe 訊息,escape 標題/摘要/trace/error 並限制長度,避免舊 Selenium stacktrace 的 `<unknown>` 造成 Telegram HTTP 400 反覆卡住;同版整合 LUDEYA 蜂王玫瑰商品線在 MOMO/PChome 名稱漂移時的 identity anchor alias。
|
||||
- **V10.371 品牌缺失同款放行**: marketplace matcher 新增 `brandless_exact_identity` 加分,只限「一側有品牌、一側缺品牌」但 shared identity anchor 夠長、規格/序列/中文名相似度都高且無 hard veto 的案例;覆蓋小米有品小浪智能感應自動噴香機,讓 PChome 標題省略品牌時仍可進入同款告警候選。
|
||||
- **V10.370 Gemini runtime sentinel**: AI automation smoke 新增 `Gemini 出站費用 sentinel`,每天檢查近 24h `ai_calls.provider='gemini'` 的 calls/tokens/cost/top callers;若 `GEMINI_API_HARD_DISABLED=true` 仍有 Gemini 記錄,smoke 直接升為 critical。scheduler 09:10 摘要推播前會先執行一次只讀 smoke,讓 Gemini 費用異常不再依賴人工打開 `/ai_automation_smoke` 才被發現。
|
||||
- **V10.369 Gemini 防復發測試與極端價差同款放行**: 新增靜態測試禁止 production code 在 `services.gemini_guard` / `config.py` 之外直接讀 `GEMINI_API_KEY`,並要求所有 Gemini SDK/REST 出站點必須經 `get_gemini_api_key()`;比價 matcher 針對「同品牌 + 明確 identity anchor + 規格完全一致」但競品價格極端偏低的原生露/眉筆案例抑制價格懲罰,避免真同款因價格差被錯降級,同時補回既有 hard-veto 安全斷言。
|
||||
- **V10.368 比價搜尋錨點強化**: marketplace matcher 補 LUDEYA 蜂王玫瑰外泌微臻霜、雅詩蘭黛微分子肌底原生露、Za / PERIPERA 眉筆眉彩等低信心邊界品牌的 identity anchor,並把「兩入組 / 任選色號 / 多色可選 / 櫻花輕盈版」歸為搜尋噪音,讓 MOMO → PChome 搜尋詞更聚焦於同款身份與規格,不被包裝組合或色號選項帶偏。
|
||||
- **V10.367 Gemini hard egress kill switch**: 新增 `GEMINI_API_HARD_DISABLED=true` 預設硬封鎖,中央 `services.gemini_guard` 會在 hard switch 未解鎖時拒絕 `GEMINI_API_KEY`,即使 `GEMINI_FALLBACK_ENABLED=true` 也不會初始化 SDK 或 REST 出站。Code Review/OpenClaw/MCP/通用 AI fallback 保留 emergency path,但必須同時設 `GEMINI_API_HARD_DISABLED=false` 與 `GEMINI_FALLBACK_ENABLED=true`,必要時再用 `GEMINI_ALLOWED_CONTEXTS` 限定 caller。
|
||||
- **V10.366 MCP runtime smoke receipt review**: 新增 `mcp_runtime_smoke_receipt` read-only builder、GET/POST endpoint、UI receipt JSON 審核面板與 deployment readiness smoke target,讓操作員貼上 `/api/market_intel/mcp_readiness?execute=true&timeout=3` 的實際收據後,判斷 external/internal MCP runtime 是否可升級為已驗收。
|
||||
- **V10.366 只讀安全邊界**: 本階段不保存 payload、不打 health、不開 DB、不抓外站、不掛 scheduler;若收據含 DB write/commit/scheduler/writes 旗標或原始 readiness blocked reasons,會直接阻擋。
|
||||
- **V10.365 專業比價分級連動**: MOMO/PChome matcher 新增 `match_type`、`price_basis`、`alert_tier` 與 evidence flags,將「高信心同款 / 同商品不同包裝 / 同系列不同款 / 可比但需覆核 / 非同款」寫入 diagnostics 與 tags;feeder、競價情報 repository、Hermes payload、NemoTron 派發與 Telegram 告警格式同步讀取同一份分級。NemoTron 也新增硬閘門:非 `exact + total_price + price_alert_exact` 的項目即使模型回傳 price alert,也會改走人工覆核,避免不同包裝或同系列不同款被直接建議降價。
|
||||
- **V10.364 111 context cap**: 111 fallback 即使降到 `llama3.2:latest`,Ollama 仍可能用 131k context 啟動 runner,導致 3B 模型也吃到 10GB+;新增 `OLLAMA_111_NUM_CTX=4096`,落到 111 時強制縮 context,並把 `llama3.2:latest` 加入零成本模型表,避免觀測台 unknown model warning。
|
||||
- **V10.363 Dashing Diva variant-safe search**: PChome/MOMO matcher 針對 Dashing Diva 美甲片補「商品頁目錄有 30片/盒、MOMO 標題省略片數」的安全豁免,只限同品牌、同美甲片線、同具名款式錨點;搜尋詞也優先帶出 `月影柔霧`、`銀絲柔彩` 等款式名,降低同系列不同款式互撞。
|
||||
- **V10.362 111 fallback shrink-to-3B**: 111 Mac 實測 `hermes3` / `qwen2.5-coder` 雖是 7B/8B,但 large context runner 仍會佔用 6-10GB RSS 並推高 swap;111 fallback 改為所有 7B+、vision 與 long-context 文字生成都降級到 `llama3.2:latest`,`ai_calls.model` 也會記錄實際降級模型並把原請求模型放入 `meta.requested_model`。
|
||||
- **V10.361 111 fallback resource guard**: 實測 111 Mac 高 load 主要來自 Codex app / WindowServer 前台負載,且 Ollama 曾因 fallback 載入 `qwen3:14b` 造成 16GB RAM / swap 壓力;已手動 unload 111 上的重模型,並讓 `OllamaService.generate()` 落到 111 時自動把 14B+ 模型降到 `OLLAMA_111_MODEL_FALLBACK`、`keep_alive` 縮至 `OLLAMA_111_KEEP_ALIVE=5m`、timeout 封頂 `OLLAMA_111_MAX_TIMEOUT=45`。GCP-A/GCP-B 仍可跑 `qwen3:14b`,111 只做短時最後備援。
|
||||
- **V10.360 browser smoke guard**: `tests/test_image_fetch.py` 改為預設 skip,只有 `RUN_MOMO_BROWSER_TESTS=1` 才會打開外部 MOMO 網站;手動執行時預設 headless,並關閉 Chrome password manager/autofill,避免一般 pytest 觸發瀏覽器與密碼允許提示。
|
||||
- **Scheduler Selenium 防彈窗**: `managed_scraper_resources()` 補 `credentials_enable_service=false`、`profile.password_manager_enabled=false` 與 Autofill/PasswordManager feature disable,降低背景 Selenium 觸發密碼管理提示的機率。
|
||||
- **PChome dashboard hot path**: `competitor_intel_repository.py` 的 coverage / review queue 最新 MOMO 價格查詢改用 `JOIN LATERAL ... ORDER BY pr.timestamp DESC, pr.id DESC LIMIT 1`,避免 window function 掃描造成首頁與覆核隊列熱路徑變慢;Dashing Diva 召回搜尋補品線與 `magicpress` broad terms。
|
||||
|
||||
### 2026-05-21:Browse.sh 爬蟲診斷與 PChome 色號比對強化
|
||||
- **V10.359 Browse.sh optional diagnostics**: 新增 `services/browse_sh_tool.py` 與 `scripts/tools/browse_sh_probe.py`,可檢查或執行 `browse` CLI;目前只定位為 MOMO/PChome selector、XHR 與 network trace 探勘,不進 scheduler 主路徑,也不直接寫正式競品價格。
|
||||
- **MOMO/PChome matcher 色號防錯配**: `marketplace_product_matcher.py` 補護甲油、洗手慕斯、足膜精準搜尋,搜尋詞保留 `4.2ml` 這類小數規格;唇釉、妝前乳、素顏霜等顯性色號/色系不一致時會 hard veto,避免同系列不同色號被推成正式價差。
|
||||
- **導入限制**: 本機 Node 16 目前因 `icu4c` 動態庫缺失無法啟動,browse.sh 需待 Node 修復或於乾淨主機安裝後才可實跑;repo 內先保留 optional wrapper、測試與 playbook。
|
||||
|
||||
### 2026-05-21:市場情報 MCP 啟用證據審核
|
||||
- **V10.358 MCP activation evidence review**: 新增 `mcp_activation_evidence` read-only builder、GET/POST endpoint、UI redacted evidence 審核面板與 deployment readiness smoke target,讓操作員貼上 env/health/router/telemetry/fallback 證據後判斷能否補齊 external/internal MCP runtime 缺口。
|
||||
- **只讀安全邊界**: 本階段不保存 payload、不打 health、不啟動 MCP、不執行 docker/SSH、不開 DB、不抓外站、不掛 scheduler;payload 只允許 redacted/boolean,真實 secret 字串與任何 DB write/fetch/scheduler 證據會被阻擋。
|
||||
|
||||
### 2026-05-21:市場情報 MCP 完整度稽核
|
||||
- **V10.357 MCP completion audit**: 新增 `mcp_completion_audit` read-only builder、GET endpoint、UI 面板與 deployment readiness smoke target,彙整外部 MCP design/runtime、內部 tool contract/runtime、activation runbook 與 fetch gate 狀態。
|
||||
- **只讀安全邊界**: 本階段只做完整度稽核,不啟動 MCP、不打 health、不執行 docker/SSH、不開 DB、不寫檔、不抓外站、不掛 scheduler;外部 MCP runtime complete 仍需 operator 依 runbook 啟用與 health 驗證。
|
||||
|
||||
### 2026-05-21:市場情報 Telegram dispatch report catalog record final closeout gate
|
||||
- **V10.356 report catalog record final closeout gate**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_final_closeout` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 archive summary gate 後覆核 catalog record identity、artifact traceability、sections、DB commit/post-write smoke、pipeline complete 與無後續 follow-up。
|
||||
- **只讀安全邊界**: 本階段是 catalog record pipeline terminal preview;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-21:市場情報 Telegram dispatch report catalog record archive summary gate
|
||||
- **V10.355 report catalog record archive summary gate**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_archive_summary` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 archive gate 後整理 catalog record identity、artifact traceability、DB commit/post-write smoke、archive manifest/retention policy 與後續 final closeout separate gate。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog record final closeout gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-21:市場情報 Telegram dispatch report catalog record archive gate
|
||||
- **V10.354 report catalog record archive gate**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_archive` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 closeout gate 後審核 closeout/commit/run receipt/writer output/post-write smoke/backup 封存證據、archive manifest/retention policy 與後續 archive summary separate gate。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog record archive summary gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-20:市場情報 Telegram dispatch report catalog record closeout gate
|
||||
- **V10.353 report catalog record closeout gate**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_closeout` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 commit gate 後審核 catalog record identity、DB commit/post-write smoke 證據、操作員 closeout 確認與後續 archive separate gate。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog record archive gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-20:市場情報 Telegram dispatch report catalog record commit gate
|
||||
- **V10.352 report catalog record commit gate**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_commit` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 run receipt 後審核外部 CLI catalog record DB commit、post-write smoke、操作員 commit gate 確認與後續 closeout separate gate。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog record closeout gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-20:市場情報 Telegram dispatch report catalog record run receipt
|
||||
- **V10.351 report catalog record run receipt**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_receipt` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 run readiness 後審核外部 CLI writer output、catalog record key/hash、DB commit receipt 與 post-write smoke。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog record commit gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不 commit、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-20:市場情報 Telegram dispatch report catalog record run readiness
|
||||
- **V10.348 report catalog record run readiness**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_readiness` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 run package 後檢查 payload manifest、manual CLI command、backup/dry-run、run receipt 與 postwrite smoke 條件。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog record run receipt gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-20:市場情報 Telegram dispatch report catalog record run package
|
||||
- **V10.347 report catalog record run package**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_package` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 record write gate 後整理 payload manifest、CLI command bundle、backup/dry-run trace 與後續 run readiness separate gate。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog record run readiness gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不執行 CLI、不寫 catalog record、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-20:市場情報 Telegram dispatch report catalog record write gate
|
||||
- **V10.344 report catalog record write gate**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_write` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 catalog write preflight 後檢查 catalog record key/schema/hash trace、operator dry-run、backup 與 commit separate gate。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog record run package gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不寫 catalog record、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-20:市場情報 Telegram dispatch report catalog write preflight
|
||||
- **V10.342 report catalog write preflight gate**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_write_preflight` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report catalog index 後整理 catalog record identity、write source trace、record schema preflight 與 runtime safety。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog record write gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫 catalog preflight file、不寫 catalog record、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-20:市場情報 Telegram dispatch report catalog index
|
||||
- **V10.339 report catalog index gate**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_index` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report catalog handoff 後整理 catalog index identity、handoff source trace、index manifest 與 runtime safety。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog write preflight gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫 catalog index file、不寫 catalog record、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-20:市場情報 Telegram dispatch report catalog handoff
|
||||
- **V10.338 report catalog handoff gate**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_handoff` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report archive summary 後整理 catalog identity、artifact manifest、section keys 與 hash traceability。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog index gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫 catalog record、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-20:市場情報 Telegram dispatch report archive summary
|
||||
- **V10.335 report archive summary gate**: 新增 `candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive_summary` service、POST endpoint、UI 按鈕與 deployment readiness smoke target,在 report archive 後整理 report identity、archive traceability、integrity review 與 runtime safety sections。
|
||||
- **只讀安全邊界**: 本階段只放行到後續 report catalog handoff gate;API/UI 不讀 approval/Telegram token、不呼叫 LLM、不補產報表、不派送 Telegram、不開 DB、不寫檔、不更新 `review_state`、不掛 scheduler。
|
||||
|
||||
### 2026-05-20:PChome 核心比價準確率與補抓可觀測性
|
||||
- **V10.334 MOMO/PChome matcher 強化**: 補常見品牌 alias 與任選/平輸/國別 noise 收斂,並加入刀把/刀片/刀頭件數解析、同品牌不同系列硬否決,避免為了提高覆蓋率把 Gillette/Schick 等不同系列錯配成同款。
|
||||
- **近門檻候選重新評分**: `CompetitorPriceFeeder` 新增 `run_retryable_candidate_revalidation()`,優先重評舊 `low_score` 中 0.70 以上、非 hard veto 且有 PChome product_id 的候選,再補抓高價未配對商品;排程與手動 API 都會納入這段流程。
|
||||
- **PChome 補抓產線狀態**: 商品看板新增 PChome backfill status card,後端以 JSON 狀態檔記錄 queued/revalidating/matching/generating_picks/clearing_cache/completed/failed、結果統計與最近 run,避免手動補抓後沒有進度與錯誤上下文。
|
||||
|
||||
### 2026-05-20:重開機後首頁熱路徑索引持久化
|
||||
- **Dashboard / PChome 慢查詢修復**: 主機重開機後 `https://mo.wooo.work/` 首頁可用但多次逾時,實際瓶頸集中在首頁與 PChome coverage 查詢掃描 `products`、`price_records`、`competitor_match_attempts`。線上先補三個索引讓首頁恢復 200,並新增 `migrations/040_dashboard_hot_path_indexes.sql` 將修復持久化到 fresh restore / DB rebuild 流程。
|
||||
- **Growth Analysis 冷快取修復**: `/growth_analysis` 在 `monthly_summary_analysis` 落後時會改掃 `realtime_sales_monthly` 聚合,冷計算約 14 秒;修正為 source fingerprint 未變時延長共享快取有效期,匯入流程仍主動清除快取,避免資料未變卻反覆掃大表。
|
||||
- **PChome backfill pilot**: V10.328 diagnostics 欄位上線後先跑小批次,刷新過期 identity_v2 30 筆(11 筆成功更新)、高價未配對 15 筆全被低信心/單位價擋下;樣本確認多數 hard veto 正確,另補 `strong_component_line_match` 處理 Gennies 類同品牌完整多組件套組。
|
||||
|
||||
### 2026-04-29:ADR-017 Phase 3f 模組化收尾啟動
|
||||
- **DB metadata 救急**: `database/manager.py` 改為顯式載入 permission / AI / autoheal / import / vendor / realtime_sales ORM,PostgreSQL 初始化透過 process-local guard + advisory lock 執行 `Base.metadata.create_all()`,避免新環境漏表與一般流量重複碰 DDL。
|
||||
- **realtime_sales_monthly 補 ORM**: 新增 `database/realtime_sales_models.py`,並同步 `docker/postgres/init/01-init.sql` 欄位,避免 fresh volume 先建出窄表後造成匯入欄位靜默遺失。
|
||||
|
||||
@@ -182,8 +182,8 @@ ssh ollama@192.168.0.188 'docker logs momo-pro-system --since 10m 2>&1 | grep -E
|
||||
|
||||
| L | 機制 | 觸發點 |
|
||||
|---|------|-------|
|
||||
| L0 | preflight 路徑檢查 | `aider_heal_executor.py:execute_code_fix` 開頭 |
|
||||
| L1 | 檔案白名單 `^(services\|routes\|database)/[a-zA-Z0-9_]+\.py$` | `ALLOWED_FILE_PATTERN` |
|
||||
| L0 | preflight 路徑檢查 | `aider_heal_executor.py:execute_code_fix` 白名單通過後 |
|
||||
| L1 | 檔案白名單 `^(services\|routes\|database)/(?:[a-zA-Z0-9_]+/)*[a-zA-Z0-9_]+\.py$`,允許子目錄但不允許 `tests/` | `ALLOWED_FILE_PATTERN` |
|
||||
| L2 | diff > 50 行拒絕 push | `AIDER_MAX_DIFF_LINES` |
|
||||
| L3 | 每小時最多 5 次 CODE_FIX | `_enforce_rate_limit` |
|
||||
| L4 | health check 失敗自動 git revert | `_revert_last_commit` |
|
||||
|
||||
@@ -7,7 +7,6 @@ restart workers but keep the preloaded app object from the old master process.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import fcntl
|
||||
import threading
|
||||
|
||||
from sqlalchemy.engine import Engine
|
||||
@@ -94,25 +93,19 @@ def post_fork(server, worker):
|
||||
|
||||
|
||||
def post_worker_init(worker):
|
||||
"""Warm the expensive dashboard cache once per container start."""
|
||||
"""Load the shared dashboard cache in every worker before user traffic."""
|
||||
enabled = os.getenv("DASHBOARD_PREWARM_ON_WORKER_INIT", "1").lower()
|
||||
if enabled in {"0", "false", "no"}:
|
||||
return
|
||||
|
||||
def _warm_dashboard_cache():
|
||||
lock_path = os.getenv("DASHBOARD_PREWARM_LOCK_PATH", "/tmp/momo-dashboard-prewarm.lock")
|
||||
try:
|
||||
with open(lock_path, "w", encoding="utf-8") as lock_file:
|
||||
try:
|
||||
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except BlockingIOError:
|
||||
worker.log.info("Dashboard cache prewarm already running; worker %s skips", worker.pid)
|
||||
return
|
||||
|
||||
from routes.dashboard_routes import warm_full_dashboard_cache
|
||||
warm_full_dashboard_cache(reason=f"gunicorn-worker-{worker.pid}")
|
||||
from routes.dashboard_routes import warm_full_dashboard_cache
|
||||
warm_full_dashboard_cache(reason=f"gunicorn-worker-{worker.pid}")
|
||||
from routes.edm_routes import warm_promo_dashboard_cache
|
||||
warm_promo_dashboard_cache(reason=f"gunicorn-worker-{worker.pid}")
|
||||
except Exception as exc:
|
||||
worker.log.warning("Dashboard cache prewarm failed in worker %s: %s", worker.pid, exc)
|
||||
worker.log.warning("Dashboard/promo cache prewarm failed in worker %s: %s", worker.pid, exc)
|
||||
|
||||
thread = threading.Thread(
|
||||
target=_warm_dashboard_cache,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# WOOO TECH - Momo Pro System
|
||||
# Kubernetes Secrets
|
||||
# =============================================================================
|
||||
# 注意:此檔案包含敏感資訊,請勿提交到 Git
|
||||
# 注意:此檔案僅允許占位符,實際敏感資訊請用 kubectl create secret 或外部 secret manager 注入
|
||||
# 使用方式:kubectl apply -f 03-secrets.yaml -n momo
|
||||
# =============================================================================
|
||||
apiVersion: v1
|
||||
@@ -13,24 +13,24 @@ metadata:
|
||||
type: Opaque
|
||||
stringData:
|
||||
# 資料庫
|
||||
DATABASE_URL: "postgresql://momo:wooo_pg_2026@momo-postgres:5432/momo_analytics"
|
||||
POSTGRES_PASSWORD: "wooo_pg_2026"
|
||||
DATABASE_URL: "postgresql://<POSTGRES_USER>:<POSTGRES_PASSWORD>@momo-postgres:5432/momo_analytics"
|
||||
POSTGRES_PASSWORD: "<POSTGRES_PASSWORD>"
|
||||
|
||||
# Flask
|
||||
SECRET_KEY: "your_flask_secret_key"
|
||||
LOGIN_PASSWORD: "0936223270"
|
||||
SECRET_KEY: "<SECRET_KEY>"
|
||||
LOGIN_PASSWORD: "<LOGIN_PASSWORD>"
|
||||
|
||||
# Email
|
||||
EMAIL_HOST_PASSWORD: "nvvnjpreldxzzas"
|
||||
EMAIL_HOST_PASSWORD: "<EMAIL_HOST_PASSWORD>"
|
||||
|
||||
# Telegram
|
||||
TELEGRAM_BOT_TOKEN: "8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg"
|
||||
TELEGRAM_BOT_TOKEN: "<TELEGRAM_BOT_TOKEN>"
|
||||
|
||||
# LINE
|
||||
LINE_CHANNEL_ACCESS_TOKEN: "nD6MSXjB2FyB111zpT6Yik5B275mi6olHjjf94VnqN1ljUcqzcA7KtSSslxsOCEG6pERzmidNJFdzol6h+9V+t1x3j4Q8ljAacqC+i0627RuwbkiLxoHTJ/9HbIdehhoSJoeuNJHLraE721iDDfIuQdB04t89/1O/w1cDnyilFU="
|
||||
LINE_CHANNEL_ACCESS_TOKEN: "<LINE_CHANNEL_ACCESS_TOKEN>"
|
||||
|
||||
# Google Gemini AI
|
||||
GEMINI_API_KEY: "AIzaSyCqv7TY2iTGi2wa91d2irwH08VYXjT9YUk"
|
||||
GEMINI_API_KEY: "<GEMINI_API_KEY>"
|
||||
|
||||
# YouTube API (趨勢爬蟲)
|
||||
YOUTUBE_API_KEY: "AIzaSyBA9n7-rYIQVMq8rSF7kz486avBAfFzJ0s"
|
||||
YOUTUBE_API_KEY: "<YOUTUBE_API_KEY>"
|
||||
|
||||
@@ -11,21 +11,21 @@ metadata:
|
||||
type: Opaque
|
||||
stringData:
|
||||
# PostgreSQL
|
||||
POSTGRES_USER: "momo"
|
||||
POSTGRES_PASSWORD: "wooo_pg_2026"
|
||||
POSTGRES_USER: "<POSTGRES_USER>"
|
||||
POSTGRES_PASSWORD: "<POSTGRES_PASSWORD>"
|
||||
|
||||
# Telegram Bot
|
||||
TELEGRAM_BOT_TOKEN: "8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg"
|
||||
TELEGRAM_CHAT_ID: "5619078117"
|
||||
TELEGRAM_BOT_TOKEN: "<TELEGRAM_BOT_TOKEN>"
|
||||
TELEGRAM_CHAT_ID: "<TELEGRAM_CHAT_ID>"
|
||||
|
||||
# LINE Notify
|
||||
LINE_NOTIFY_TOKEN: "nD6MSXjB2FyB111zpT6Yik5B275mi6olHjjf94VnqN1ljUcqzcA7KtSSslxsOCEG6pERzmidNJFdzol6h+9V+t1x3j4Q8ljAacqC+i0627RuwbkiLxoHTJ/9HbIdehhoSJoeuNJHLraE721iDDfIuQdB04t89/1O/w1cDnyilFU="
|
||||
LINE_NOTIFY_TOKEN: "<LINE_NOTIFY_TOKEN>"
|
||||
|
||||
# Gemini AI
|
||||
GEMINI_API_KEY: "AIzaSyCqv7TY2iTGi2wa91d2irwH08VYXjT9YUk"
|
||||
GEMINI_API_KEY: "<GEMINI_API_KEY>"
|
||||
|
||||
# Ollama AI
|
||||
OLLAMA_API_KEY: "0df8b4f247a4497998248f013ce92a17.vqSWDEK0RppTZIwcdT-ei-Sz"
|
||||
OLLAMA_API_KEY: "<OLLAMA_API_KEY>"
|
||||
|
||||
# App Password
|
||||
APP_PASSWORD: "0936223270"
|
||||
APP_PASSWORD: "<APP_PASSWORD>"
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
apiVersion: v1
|
||||
data:
|
||||
google_credentials.json: eyJpbnN0YWxsZWQiOnsiY2xpZW50X2lkIjoiMTMyODIzMDc5MzI2LWg5Y3ZqNWVhaGlnbThocDlxMGI3dDVyazc3Ymh1M2dwLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwicHJvamVjdF9pZCI6Indvb28tNDgxMjA0IiwiYXV0aF91cmkiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20vby9vYXV0aDIvYXV0aCIsInRva2VuX3VyaSI6Imh0dHBzOi8vb2F1dGgyLmdvb2dsZWFwaXMuY29tL3Rva2VuIiwiYXV0aF9wcm92aWRlcl94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vb2F1dGgyL3YxL2NlcnRzIiwiY2xpZW50X3NlY3JldCI6IkdPQ1NQWC1PSHJRckdkN0pkalN2RkdoUkhXckNfUTFvcUxmIiwicmVkaXJlY3RfdXJpcyI6WyJodHRwOi8vbG9jYWxob3N0Il19fQ==
|
||||
google_token.pickle: gASV8gMAAAAAAACMGWdvb2dsZS5vYXV0aDIuY3JlZGVudGlhbHOUjAtDcmVkZW50aWFsc5STlCmBlH2UKIwFdG9rZW6UjP55YTI5LmEwQVVNV2dfTGdqc0x5S0dUZXRIcWRlTXY1eHdWUTlhNU1tVy1FMVVrdzNSeUV2MWFacnhCWThKckhlR19HQ2NnQVV4RkpJX0taLTdnd2gtZ3p6bzNRdGpoLXBVdDdQWndyWW5QVzI1RGhBTlFOWVJGMU8zSWZqeUV2REc2cmd2R1lhcWxrWG9ZSm81Z3RKLWRHVW9ZNy1tSWRIam1adHd5TXFzN2VqZ25GanZia0tOUk51QzhHN1EtMWNhb3dTWGkxaXFKTUNEZERhQ2dZS0FTQVNBUklTRlFIR1gyTWktOHNwS2JrRmdSOGQ3ZWdSbkFRaTl3MDIwN5SMBmV4cGlyeZSMCGRhdGV0aW1llIwIZGF0ZXRpbWWUk5RDCgfqARcHKTcNVuqUhZRSlIwOX3JlZnJlc2hfdG9rZW6UjGcxLy8wZVZmd2hzV2NoS2taQ2dZSUFSQUFHQTRTTndGLUw5SXJPX0FvcDBkSnJUMVo4LWV2ZDdmSEVKS2o0WFBfNmVrT1BMUGNoUjlhQzg0Tkt6S2QzQmdTZjNnZnJpREV5VU50bkZnlIwJX2lkX3Rva2VulE6MB19zY29wZXOUXZSMJWh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZHJpdmWUYYwPX2RlZmF1bHRfc2NvcGVzlE6MD19ncmFudGVkX3Njb3Blc5RdlIwlaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vYXV0aC9kcml2ZZRhjApfdG9rZW5fdXJplIwjaHR0cHM6Ly9vYXV0aDIuZ29vZ2xlYXBpcy5jb20vdG9rZW6UjApfY2xpZW50X2lklIxIMTMyODIzMDc5MzI2LWg5Y3ZqNWVhaGlnbThocDlxMGI3dDVyazc3Ymh1M2dwLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tlIwOX2NsaWVudF9zZWNyZXSUjCNHT0NTUFgtT0hyUXJHZDdKZGpTdkZHaFJIV3JDX1Exb3FMZpSMEV9xdW90YV9wcm9qZWN0X2lklE6MC19yYXB0X3Rva2VulE6MFl9lbmFibGVfcmVhdXRoX3JlZnJlc2iUiYwPX3RydXN0X2JvdW5kYXJ5lE6MEF91bml2ZXJzZV9kb21haW6UjA5nb29nbGVhcGlzLmNvbZSMD19jcmVkX2ZpbGVfcGF0aJROjBlfdXNlX25vbl9ibG9ja2luZ19yZWZyZXNolImMCF9hY2NvdW50lIwAlHViLg==
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: google-drive-credentials
|
||||
namespace: momo
|
||||
type: Opaque
|
||||
stringData:
|
||||
# 實際部署請透過 kubectl create secret 或外部 secret manager 注入。
|
||||
google_credentials.json: "<GOOGLE_DRIVE_CREDENTIALS_JSON>"
|
||||
google_token.pickle: "<GOOGLE_DRIVE_TOKEN_PICKLE_BASE64>"
|
||||
|
||||
@@ -11,29 +11,29 @@ metadata:
|
||||
type: Opaque
|
||||
stringData:
|
||||
# PostgreSQL
|
||||
POSTGRES_USER: "momo"
|
||||
POSTGRES_PASSWORD: "wooo_pg_2026"
|
||||
DATABASE_URL: "postgresql://momo:wooo_pg_2026@momo-postgres:5432/momo_analytics"
|
||||
POSTGRES_USER: "<POSTGRES_USER>"
|
||||
POSTGRES_PASSWORD: "<POSTGRES_PASSWORD>"
|
||||
DATABASE_URL: "postgresql://<POSTGRES_USER>:<POSTGRES_PASSWORD>@momo-postgres:5432/momo_analytics"
|
||||
|
||||
# App 認證
|
||||
SECRET_KEY: "wooo-momo-secret-key-2026"
|
||||
LOGIN_PASSWORD: "0936223270"
|
||||
SECRET_KEY: "<SECRET_KEY>"
|
||||
LOGIN_PASSWORD: "<LOGIN_PASSWORD>"
|
||||
|
||||
# Telegram Bot
|
||||
TELEGRAM_BOT_TOKEN: "8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg"
|
||||
TELEGRAM_CHAT_ID: "5619078117"
|
||||
TELEGRAM_BOT_TOKEN: "<TELEGRAM_BOT_TOKEN>"
|
||||
TELEGRAM_CHAT_ID: "<TELEGRAM_CHAT_ID>"
|
||||
|
||||
# LINE Notify
|
||||
LINE_CHANNEL_ACCESS_TOKEN: "nD6MSXjB2FyB111zpT6Yik5B275mi6olHjjf94VnqN1ljUcqzcA7KtSSslxsOCEG6pERzmidNJFdzol6h+9V+t1x3j4Q8ljAacqC+i0627RuwbkiLxoHTJ/9HbIdehhoSJoeuNJHLraE721iDDfIuQdB04t89/1O/w1cDnyilFU="
|
||||
LINE_CHANNEL_ACCESS_TOKEN: "<LINE_CHANNEL_ACCESS_TOKEN>"
|
||||
|
||||
# Email
|
||||
EMAIL_HOST_PASSWORD: ""
|
||||
|
||||
# Gemini AI
|
||||
GEMINI_API_KEY: "AIzaSyCqv7TY2iTGi2wa91d2irwH08VYXjT9YUk"
|
||||
GEMINI_API_KEY: "<GEMINI_API_KEY>"
|
||||
|
||||
# Ollama AI (GCP 可能無法連到內網,視情況調整)
|
||||
OLLAMA_API_KEY: "0df8b4f247a4497998248f013ce92a17.vqSWDEK0RppTZIwcdT-ei-Sz"
|
||||
OLLAMA_API_KEY: "<OLLAMA_API_KEY>"
|
||||
|
||||
# App Password
|
||||
APP_PASSWORD: "0936223270"
|
||||
APP_PASSWORD: "<APP_PASSWORD>"
|
||||
|
||||
@@ -45,7 +45,7 @@ data:
|
||||
|
||||
# Ollama AI 服務(ADR-027 三主機級聯:GCP-A → GCP-B → 111)
|
||||
# GCP K8s 直接走 GCP Ollama 兩台公網 IP,failback 才走 111 內網。
|
||||
OLLAMA_HOST_PRIMARY: "http://34.143.170.20:11434"
|
||||
OLLAMA_HOST_PRIMARY: "http://34.87.90.216:11434"
|
||||
OLLAMA_HOST_SECONDARY: "http://34.21.145.224:11434"
|
||||
OLLAMA_HOST_FALLBACK: "http://192.168.0.111:11434"
|
||||
OLLAMA_MODEL: "qwen3:8b"
|
||||
|
||||
@@ -75,8 +75,8 @@ alertmanager:
|
||||
- name: 'null'
|
||||
- name: 'telegram'
|
||||
telegram_configs:
|
||||
- bot_token: '8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg'
|
||||
chat_id: 5619078117
|
||||
- bot_token: '<TELEGRAM_BOT_TOKEN>'
|
||||
chat_id: '<TELEGRAM_CHAT_ID>'
|
||||
parse_mode: 'HTML'
|
||||
message: |
|
||||
{{ if eq .Status "firing" }}🚨🔥 <b>告警觸發</b> 🔥🚨{{ else }}✅💚 <b>告警恢復</b> 💚✅{{ end }}
|
||||
|
||||
@@ -7,10 +7,10 @@ metadata:
|
||||
namespace: tools
|
||||
type: Opaque
|
||||
stringData:
|
||||
SUPERSET_SECRET_KEY: "wooo-superset-secret-key-2026-very-long-string"
|
||||
ADMIN_PASSWORD: "Wooo_Superset_2026"
|
||||
DATABASE_PASSWORD: "superset_db_2026"
|
||||
REDIS_PASSWORD: ""
|
||||
SUPERSET_SECRET_KEY: "<SUPERSET_SECRET_KEY>"
|
||||
ADMIN_PASSWORD: "<SUPERSET_ADMIN_PASSWORD>"
|
||||
DATABASE_PASSWORD: "<SUPERSET_DATABASE_PASSWORD>"
|
||||
REDIS_PASSWORD: "<SUPERSET_REDIS_PASSWORD>"
|
||||
|
||||
---
|
||||
# Superset Redis
|
||||
@@ -220,7 +220,7 @@ spec:
|
||||
name: superset-secret
|
||||
key: ADMIN_PASSWORD
|
||||
- name: DATABASE_URL
|
||||
value: "postgresql+psycopg2://superset:superset_db_2026@superset-postgres:5432/superset"
|
||||
value: "postgresql+psycopg2://superset:<SUPERSET_DATABASE_PASSWORD>@superset-postgres:5432/superset"
|
||||
volumeMounts:
|
||||
- name: superset-config
|
||||
mountPath: /app/pythonpath/superset_config.py
|
||||
@@ -242,7 +242,7 @@ spec:
|
||||
name: superset-secret
|
||||
key: SUPERSET_SECRET_KEY
|
||||
- name: DATABASE_URL
|
||||
value: "postgresql+psycopg2://superset:superset_db_2026@superset-postgres:5432/superset"
|
||||
value: "postgresql+psycopg2://superset:<SUPERSET_DATABASE_PASSWORD>@superset-postgres:5432/superset"
|
||||
- name: REDIS_HOST
|
||||
value: "superset-redis"
|
||||
volumeMounts:
|
||||
|
||||
@@ -86,7 +86,7 @@ CREATE TABLE IF NOT EXISTS ai_calls (
|
||||
-- ─────── critic-A11 修補:白名單 + PII/膨脹護欄 ───────
|
||||
-- H1: provider 白名單(NOT VALID 不檢既存資料,僅檢未來寫入)
|
||||
-- 三主機架構(統帥 2026-05-03 確認):
|
||||
-- gcp_ollama = Primary 34.143.170.20 (SSD)
|
||||
-- gcp_ollama = Primary 34.87.90.216 (SSD)
|
||||
-- ollama_secondary = Secondary 34.21.145.224 (SSD)
|
||||
-- ollama_111 = Fallback 192.168.0.111 (HDD/Local)
|
||||
CONSTRAINT chk_ai_calls_provider CHECK (
|
||||
|
||||
@@ -22,7 +22,7 @@ CREATE TABLE IF NOT EXISTS host_health_probes (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
probed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
host_label VARCHAR(64) NOT NULL, -- 'Primary (GCP)' / 'Secondary (GCP)' / 'Fallback (111)'
|
||||
host_url VARCHAR(256) NOT NULL, -- http://34.143.170.20:11434 等
|
||||
host_url VARCHAR(256) NOT NULL, -- http://34.87.90.216:11434 等
|
||||
healthy BOOLEAN NOT NULL,
|
||||
unhealthy_mark BOOLEAN NOT NULL DEFAULT FALSE, -- 對應 _is_unhealthy(host)
|
||||
models_count INTEGER DEFAULT 0, -- 載入模型數
|
||||
|
||||
@@ -220,6 +220,55 @@ CREATE INDEX IF NOT EXISTS idx_market_crawler_run_platform_time
|
||||
CREATE INDEX IF NOT EXISTS idx_market_crawler_run_status_time
|
||||
ON market_crawler_runs (status, started_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS market_alert_review_queue (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
alert_candidate_id VARCHAR(120) NOT NULL UNIQUE,
|
||||
review_state VARCHAR(40) NOT NULL DEFAULT 'draft',
|
||||
priority_lane VARCHAR(40) NOT NULL DEFAULT 'watch',
|
||||
threshold_level VARCHAR(40) NOT NULL,
|
||||
total_score DOUBLE PRECISION NOT NULL DEFAULT 0.0,
|
||||
evidence_bundle_id VARCHAR(120) NOT NULL,
|
||||
dedupe_key VARCHAR(240) NOT NULL,
|
||||
source_batch_id VARCHAR(80) NOT NULL,
|
||||
campaign_id BIGINT REFERENCES market_campaigns(id),
|
||||
market_product_id BIGINT REFERENCES market_campaign_products(id),
|
||||
momo_i_code VARCHAR(50),
|
||||
reviewer_identity VARCHAR(120),
|
||||
review_action VARCHAR(60),
|
||||
review_reason TEXT,
|
||||
reviewed_at TIMESTAMP,
|
||||
previous_state VARCHAR(40),
|
||||
next_state VARCHAR(40),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
metadata_json TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_market_alert_review_queue_alert_candidate_id
|
||||
ON market_alert_review_queue (alert_candidate_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_market_alert_review_queue_state
|
||||
ON market_alert_review_queue (review_state);
|
||||
CREATE INDEX IF NOT EXISTS idx_market_alert_review_queue_priority_lane
|
||||
ON market_alert_review_queue (priority_lane);
|
||||
CREATE INDEX IF NOT EXISTS idx_market_alert_review_queue_threshold_level
|
||||
ON market_alert_review_queue (threshold_level);
|
||||
CREATE INDEX IF NOT EXISTS idx_market_alert_review_queue_evidence_bundle_id
|
||||
ON market_alert_review_queue (evidence_bundle_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_market_alert_review_queue_source_batch_id
|
||||
ON market_alert_review_queue (source_batch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_market_alert_review_queue_campaign_id
|
||||
ON market_alert_review_queue (campaign_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_market_alert_review_queue_market_product_id
|
||||
ON market_alert_review_queue (market_product_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_market_alert_review_queue_momo_i_code
|
||||
ON market_alert_review_queue (momo_i_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_market_alert_review_queue_state_priority
|
||||
ON market_alert_review_queue (review_state, priority_lane, created_at);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_market_alert_review_queue_dedupe
|
||||
ON market_alert_review_queue (dedupe_key);
|
||||
CREATE INDEX IF NOT EXISTS idx_market_alert_review_queue_bundle
|
||||
ON market_alert_review_queue (evidence_bundle_id, source_batch_id);
|
||||
|
||||
GRANT ALL PRIVILEGES ON market_platforms TO momo;
|
||||
GRANT ALL PRIVILEGES ON market_campaigns TO momo;
|
||||
GRANT ALL PRIVILEGES ON market_campaign_snapshots TO momo;
|
||||
@@ -227,6 +276,7 @@ GRANT ALL PRIVILEGES ON market_campaign_products TO momo;
|
||||
GRANT ALL PRIVILEGES ON market_product_price_history TO momo;
|
||||
GRANT ALL PRIVILEGES ON market_product_matches TO momo;
|
||||
GRANT ALL PRIVILEGES ON market_crawler_runs TO momo;
|
||||
GRANT ALL PRIVILEGES ON market_alert_review_queue TO momo;
|
||||
|
||||
GRANT USAGE, SELECT ON SEQUENCE market_platforms_id_seq TO momo;
|
||||
GRANT USAGE, SELECT ON SEQUENCE market_campaigns_id_seq TO momo;
|
||||
@@ -235,3 +285,4 @@ GRANT USAGE, SELECT ON SEQUENCE market_campaign_products_id_seq TO momo;
|
||||
GRANT USAGE, SELECT ON SEQUENCE market_product_price_history_id_seq TO momo;
|
||||
GRANT USAGE, SELECT ON SEQUENCE market_product_matches_id_seq TO momo;
|
||||
GRANT USAGE, SELECT ON SEQUENCE market_crawler_runs_id_seq TO momo;
|
||||
GRANT USAGE, SELECT ON SEQUENCE market_alert_review_queue_id_seq TO momo;
|
||||
|
||||
26
migrations/038_create_ppt_generation_runs.sql
Normal file
26
migrations/038_create_ppt_generation_runs.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- 038_create_ppt_generation_runs.sql
|
||||
-- PPT 定期產出嘗試紀錄:保留每次日/週/月/季/半年/年排程的狀態與結果。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ppt_generation_runs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
schedule_kind VARCHAR(40) NOT NULL,
|
||||
report_type VARCHAR(50) NOT NULL,
|
||||
target_label VARCHAR(160),
|
||||
status VARCHAR(30) NOT NULL,
|
||||
parameters_json TEXT,
|
||||
file_path VARCHAR(500),
|
||||
file_size INTEGER,
|
||||
error_msg TEXT,
|
||||
result_payload TEXT,
|
||||
started_at TIMESTAMP WITHOUT TIME ZONE,
|
||||
finished_at TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_ppt_generation_runs_report_type
|
||||
ON ppt_generation_runs (report_type);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_ppt_generation_runs_started_at
|
||||
ON ppt_generation_runs (started_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_ppt_generation_runs_schedule_kind
|
||||
ON ppt_generation_runs (schedule_kind);
|
||||
52
migrations/039_create_competitor_match_reviews.sql
Normal file
52
migrations/039_create_competitor_match_reviews.sql
Normal file
@@ -0,0 +1,52 @@
|
||||
-- =============================================================================
|
||||
-- Migration 039: 競品比對人工覆核決策表
|
||||
-- MOMO PRO — PChome match human-in-the-loop review
|
||||
-- 2026-05-20 台北
|
||||
-- =============================================================================
|
||||
-- 說明:
|
||||
-- competitor_match_attempts 保存 matcher / feeder 每一次嘗試;
|
||||
-- competitor_match_reviews 保存人工對最佳候選的處置結果。
|
||||
-- 只有 review_action='accept_identity' 才會由服務層寫入 competitor_prices,
|
||||
-- reject_identity / unit_price_required 只關閉覆核與保存決策,不污染正式價差。
|
||||
-- =============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS competitor_match_reviews (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
|
||||
sku VARCHAR(50) NOT NULL,
|
||||
source VARCHAR(30) NOT NULL DEFAULT 'pchome',
|
||||
|
||||
review_action VARCHAR(40) NOT NULL,
|
||||
review_reason TEXT,
|
||||
reviewer_identity VARCHAR(120),
|
||||
|
||||
momo_product_id INTEGER,
|
||||
momo_product_name TEXT,
|
||||
momo_price NUMERIC(10,2),
|
||||
|
||||
candidate_product_id VARCHAR(100),
|
||||
candidate_product_name TEXT,
|
||||
candidate_price NUMERIC(10,2),
|
||||
candidate_match_score NUMERIC(4,3),
|
||||
candidate_diagnostic TEXT,
|
||||
|
||||
resulting_attempt_status VARCHAR(40),
|
||||
reviewed_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_comp_match_reviews_sku_source_time
|
||||
ON competitor_match_reviews (sku, source, reviewed_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_comp_match_reviews_action_time
|
||||
ON competitor_match_reviews (review_action, reviewed_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_comp_match_reviews_candidate
|
||||
ON competitor_match_reviews (candidate_product_id);
|
||||
|
||||
GRANT ALL PRIVILEGES ON competitor_match_reviews TO momo;
|
||||
GRANT USAGE, SELECT ON SEQUENCE competitor_match_reviews_id_seq TO momo;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '✅ Migration 039 完成 — competitor_match_reviews 人工覆核決策表已建立';
|
||||
END $$;
|
||||
37
migrations/040_dashboard_hot_path_indexes.sql
Normal file
37
migrations/040_dashboard_hot_path_indexes.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
-- =============================================================================
|
||||
-- Migration 040: Dashboard / PChome 熱路徑索引
|
||||
-- MOMO PRO — 2026-05-20 重開機後首頁慢查詢修復持久化
|
||||
-- =============================================================================
|
||||
-- 背景:
|
||||
-- 2026-05-20 主機重開機後,https://mo.wooo.work/ 首頁雖可用但多次
|
||||
-- 8-45 秒逾時。實際瓶頸在首頁與 PChome coverage 查詢反覆掃描
|
||||
-- products、price_records、competitor_match_attempts。
|
||||
--
|
||||
-- 設計:
|
||||
-- 1. 只新增索引,不改資料、不調整欄位、不重啟容器。
|
||||
-- 2. 使用 IF NOT EXISTS,讓 live 已手動套用索引、fresh restore、重跑 migration
|
||||
-- 都維持冪等。
|
||||
-- 3. 不使用 CREATE INDEX CONCURRENTLY,避免被包在 transaction 的 migration
|
||||
-- runner 執行時失敗;若未來超大型 live DB 需要零鎖定窗口,請在維護窗口
|
||||
-- 手動轉換為 CONCURRENTLY 流程。
|
||||
-- =============================================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_comp_match_attempts_source_sku_attempted_at
|
||||
ON competitor_match_attempts (source, sku, attempted_at DESC)
|
||||
INCLUDE (attempt_status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_price_records_product_timestamp_id_desc
|
||||
ON price_records (product_id, timestamp DESC, id DESC)
|
||||
INCLUDE (price);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_products_status_id_icode
|
||||
ON products (status, id, i_code);
|
||||
|
||||
ANALYZE products;
|
||||
ANALYZE price_records;
|
||||
ANALYZE competitor_match_attempts;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'Migration 040 完成 — Dashboard / PChome 熱路徑索引已確認';
|
||||
END $$;
|
||||
49
migrations/041_competitor_match_diagnostics.sql
Normal file
49
migrations/041_competitor_match_diagnostics.sql
Normal file
@@ -0,0 +1,49 @@
|
||||
-- =============================================================================
|
||||
-- Migration 041: PChome / MOMO 比價診斷欄位
|
||||
-- MOMO PRO — Core price comparison evidence
|
||||
-- 2026-05-20 台北
|
||||
-- =============================================================================
|
||||
-- 說明:
|
||||
-- competitor_prices / competitor_price_history / competitor_match_attempts
|
||||
-- 補存 PChome URL、圖片、庫存與 matcher 結構化診斷。
|
||||
-- 目的不是放寬配對門檻,而是讓「低信心 / 身份否決 / 單位價」可被
|
||||
-- 商品列表、圖表、簡報與人工覆核精準追蹤。
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE IF EXISTS competitor_prices
|
||||
ADD COLUMN IF NOT EXISTS competitor_product_url TEXT,
|
||||
ADD COLUMN IF NOT EXISTS competitor_image_url TEXT,
|
||||
ADD COLUMN IF NOT EXISTS competitor_stock INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS match_diagnostic_json JSONB,
|
||||
ADD COLUMN IF NOT EXISTS comparison_mode VARCHAR(40),
|
||||
ADD COLUMN IF NOT EXISTS hard_veto BOOLEAN,
|
||||
ADD COLUMN IF NOT EXISTS diagnostic_codes JSONB;
|
||||
|
||||
ALTER TABLE IF EXISTS competitor_price_history
|
||||
ADD COLUMN IF NOT EXISTS competitor_product_url TEXT,
|
||||
ADD COLUMN IF NOT EXISTS competitor_image_url TEXT,
|
||||
ADD COLUMN IF NOT EXISTS competitor_stock INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS match_diagnostic_json JSONB,
|
||||
ADD COLUMN IF NOT EXISTS comparison_mode VARCHAR(40),
|
||||
ADD COLUMN IF NOT EXISTS hard_veto BOOLEAN,
|
||||
ADD COLUMN IF NOT EXISTS diagnostic_codes JSONB;
|
||||
|
||||
ALTER TABLE IF EXISTS competitor_match_attempts
|
||||
ADD COLUMN IF NOT EXISTS competitor_product_url TEXT,
|
||||
ADD COLUMN IF NOT EXISTS competitor_image_url TEXT,
|
||||
ADD COLUMN IF NOT EXISTS competitor_stock INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS match_diagnostic_json JSONB,
|
||||
ADD COLUMN IF NOT EXISTS comparison_mode VARCHAR(40),
|
||||
ADD COLUMN IF NOT EXISTS hard_veto BOOLEAN,
|
||||
ADD COLUMN IF NOT EXISTS diagnostic_codes JSONB;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_comp_match_attempts_mode_time
|
||||
ON competitor_match_attempts (comparison_mode, attempted_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_comp_price_comparison_mode
|
||||
ON competitor_prices (comparison_mode);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '✅ Migration 041 完成 — PChome/MOMO 比價診斷欄位已補齊';
|
||||
END $$;
|
||||
23
migrations/042_add_browse_diagnostics_to_match_attempts.sql
Normal file
23
migrations/042_add_browse_diagnostics_to_match_attempts.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
-- =============================================================================
|
||||
-- Migration 042: PChome browse.sh 診斷計畫欄位
|
||||
-- MOMO PRO — Price comparison fallback observability
|
||||
-- 2026-05-21 台北
|
||||
-- =============================================================================
|
||||
-- 說明:
|
||||
-- competitor_match_attempts 補存 browse.sh 診斷計畫 JSON。
|
||||
-- 正式價格爬蟲仍維持 API-first;此欄位只保存低信心、無結果、
|
||||
-- 單位價覆核或外部爬蟲錯誤時的 read-only probe plan,供人工或
|
||||
-- opt-in 執行 `browse get/open` 做 selector / XHR / 商品頁取證。
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE IF EXISTS competitor_match_attempts
|
||||
ADD COLUMN IF NOT EXISTS browse_diagnostic_json JSONB;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_comp_match_attempts_browse_diag_time
|
||||
ON competitor_match_attempts (attempted_at DESC)
|
||||
WHERE browse_diagnostic_json IS NOT NULL;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '✅ Migration 042 完成 — PChome browse.sh 診斷計畫欄位已補齊';
|
||||
END $$;
|
||||
32
migrations/043_allow_ollama_other_ai_calls_provider.sql
Normal file
32
migrations/043_allow_ollama_other_ai_calls_provider.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- Migration 043: allow telemetry-only ollama_other provider
|
||||
-- Date: 2026-06-01
|
||||
--
|
||||
-- Runtime code maps unresolved or unknown Ollama hosts to `ollama_other`.
|
||||
-- This is not a routing target; it is an observability bucket for failures
|
||||
-- that happen before a concrete GCP-A/GCP-B/111 host is selected.
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE IF EXISTS ai_calls
|
||||
DROP CONSTRAINT IF EXISTS chk_ai_calls_provider;
|
||||
|
||||
ALTER TABLE IF EXISTS ai_calls
|
||||
ADD CONSTRAINT chk_ai_calls_provider
|
||||
CHECK (
|
||||
provider IN (
|
||||
'gcp_ollama',
|
||||
'ollama_secondary',
|
||||
'ollama_111',
|
||||
'ollama_other',
|
||||
'gemini',
|
||||
'claude',
|
||||
'nim',
|
||||
'openrouter',
|
||||
'nim_via_elephant'
|
||||
)
|
||||
) NOT VALID;
|
||||
|
||||
COMMENT ON CONSTRAINT chk_ai_calls_provider ON ai_calls IS
|
||||
'Provider telemetry whitelist; ollama_other is unresolved/unknown Ollama host telemetry, not a route.';
|
||||
|
||||
COMMIT;
|
||||
175
migrations/044_external_market_offer_normalization.sql
Normal file
175
migrations/044_external_market_offer_normalization.sql
Normal file
@@ -0,0 +1,175 @@
|
||||
-- =============================================================================
|
||||
-- Migration 044: external market source / offer normalization
|
||||
-- MOMO PRO / PChome revenue growth automation
|
||||
-- 2026-06-15 Taipei
|
||||
-- =============================================================================
|
||||
-- Notes:
|
||||
-- Additive only. This migration does not drop, truncate, or rewrite existing
|
||||
-- competitor_prices / competitor_price_history / market_* tables.
|
||||
-- =============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS external_market_sources (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
code VARCHAR(80) NOT NULL UNIQUE,
|
||||
display_name VARCHAR(160) NOT NULL,
|
||||
platform_code VARCHAR(80) NOT NULL,
|
||||
source_kind VARCHAR(60) NOT NULL,
|
||||
status VARCHAR(40) NOT NULL DEFAULT 'paused',
|
||||
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
write_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
allowed_input_methods_json TEXT,
|
||||
quality_policy_json TEXT,
|
||||
plain_note TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_external_market_sources_code
|
||||
ON external_market_sources (code);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_market_sources_platform_code
|
||||
ON external_market_sources (platform_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_market_sources_source_kind
|
||||
ON external_market_sources (source_kind);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_market_sources_status
|
||||
ON external_market_sources (status, enabled);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS external_offers (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
source_code VARCHAR(80) NOT NULL REFERENCES external_market_sources(code),
|
||||
platform_code VARCHAR(80) NOT NULL,
|
||||
source_product_id VARCHAR(220) NOT NULL,
|
||||
source_offer_key VARCHAR(260) NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
brand VARCHAR(180),
|
||||
category_text VARCHAR(320),
|
||||
product_url TEXT,
|
||||
image_url TEXT,
|
||||
price DOUBLE PRECISION,
|
||||
original_price DOUBLE PRECISION,
|
||||
currency VARCHAR(12) NOT NULL DEFAULT 'TWD',
|
||||
stock_status VARCHAR(80),
|
||||
sold_count INTEGER,
|
||||
rating DOUBLE PRECISION,
|
||||
review_count INTEGER,
|
||||
observed_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMP,
|
||||
ingestion_method VARCHAR(60) NOT NULL,
|
||||
connector_key VARCHAR(120),
|
||||
pchome_product_id VARCHAR(120),
|
||||
momo_sku VARCHAR(80),
|
||||
match_status VARCHAR(40) NOT NULL DEFAULT 'unmatched',
|
||||
quality_score DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
data_quality_status VARCHAR(40) NOT NULL DEFAULT 'needs_review',
|
||||
quality_notes_json TEXT,
|
||||
raw_payload_json TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT uq_external_offer_source_product_observed
|
||||
UNIQUE (source_code, source_product_id, observed_at, ingestion_method)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_source_code
|
||||
ON external_offers (source_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_platform_code
|
||||
ON external_offers (platform_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_source_product_id
|
||||
ON external_offers (source_product_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_brand
|
||||
ON external_offers (brand);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_category_text
|
||||
ON external_offers (category_text);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_stock_status
|
||||
ON external_offers (stock_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_observed_at
|
||||
ON external_offers (observed_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_expires_at
|
||||
ON external_offers (expires_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_ingestion_method
|
||||
ON external_offers (ingestion_method);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_connector_key
|
||||
ON external_offers (connector_key);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_pchome_product_id
|
||||
ON external_offers (pchome_product_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_momo_sku
|
||||
ON external_offers (momo_sku);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_match_status
|
||||
ON external_offers (match_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_data_quality_status
|
||||
ON external_offers (data_quality_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_source_seen
|
||||
ON external_offers (source_code, observed_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_platform_product
|
||||
ON external_offers (platform_code, source_product_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_pchome_product
|
||||
ON external_offers (pchome_product_id, source_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_external_offers_match_quality
|
||||
ON external_offers (match_status, data_quality_status, quality_score);
|
||||
|
||||
INSERT INTO external_market_sources (
|
||||
code,
|
||||
display_name,
|
||||
platform_code,
|
||||
source_kind,
|
||||
status,
|
||||
enabled,
|
||||
write_enabled,
|
||||
allowed_input_methods_json,
|
||||
quality_policy_json,
|
||||
plain_note
|
||||
) VALUES
|
||||
(
|
||||
'momo_reference',
|
||||
'MOMO 外部價格參考',
|
||||
'momo',
|
||||
'legacy_bridge',
|
||||
'active',
|
||||
TRUE,
|
||||
FALSE,
|
||||
'["legacy_competitor_cache","manual_csv","provider_api"]',
|
||||
'{"minimum_match_status":"verified","minimum_quality_score":76}',
|
||||
'目前只採用已確認同款的 MOMO 參考價。'
|
||||
),
|
||||
(
|
||||
'shopee',
|
||||
'蝦皮',
|
||||
'shopee',
|
||||
'connector_contract',
|
||||
'paused',
|
||||
FALSE,
|
||||
FALSE,
|
||||
'["official_api","provider_api","manual_csv"]',
|
||||
'{"minimum_match_status":"verified","manual_review_required":true}',
|
||||
'先暫停,不進告警;未來可接官方 API、供應商資料或手動 CSV。'
|
||||
),
|
||||
(
|
||||
'coupang',
|
||||
'酷澎',
|
||||
'coupang',
|
||||
'connector_contract',
|
||||
'paused',
|
||||
FALSE,
|
||||
FALSE,
|
||||
'["official_api","provider_api","manual_csv"]',
|
||||
'{"minimum_match_status":"verified","manual_review_required":true}',
|
||||
'先暫停,不進告警;未來可接官方 API、供應商資料或手動 CSV。'
|
||||
)
|
||||
ON CONFLICT (code) DO UPDATE SET
|
||||
display_name = EXCLUDED.display_name,
|
||||
platform_code = EXCLUDED.platform_code,
|
||||
source_kind = EXCLUDED.source_kind,
|
||||
status = EXCLUDED.status,
|
||||
enabled = EXCLUDED.enabled,
|
||||
allowed_input_methods_json = EXCLUDED.allowed_input_methods_json,
|
||||
quality_policy_json = EXCLUDED.quality_policy_json,
|
||||
plain_note = EXCLUDED.plain_note,
|
||||
updated_at = NOW();
|
||||
|
||||
GRANT ALL PRIVILEGES ON external_market_sources TO momo;
|
||||
GRANT ALL PRIVILEGES ON external_offers TO momo;
|
||||
GRANT USAGE, SELECT ON SEQUENCE external_market_sources_id_seq TO momo;
|
||||
GRANT USAGE, SELECT ON SEQUENCE external_offers_id_seq TO momo;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'Migration 044 complete: external market source and offer normalization is ready';
|
||||
END $$;
|
||||
@@ -59,7 +59,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
@@ -98,7 +98,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST", "url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"method": "POST", "url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true, "specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST", "url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"method": "POST", "url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true, "specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
},
|
||||
@@ -51,7 +51,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST", "url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"method": "POST", "url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true, "specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST", "url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"method": "POST", "url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true, "specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
},
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST", "url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"method": "POST", "url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true, "specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST", "url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"method": "POST", "url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true, "specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST", "url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"method": "POST", "url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true, "specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST", "url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"method": "POST", "url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true, "specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
},
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST", "url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"method": "POST", "url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true, "specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
},
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST", "url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"method": "POST", "url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true, "specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
},
|
||||
@@ -76,7 +76,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST", "url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"method": "POST", "url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true, "specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
},
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "sshPassword",
|
||||
"resource": "command",
|
||||
"command": "echo '0936223270' | sudo -S kubectl rollout restart deployment momo-app -n momo && sleep 60 && curl -s -o /dev/null -w '%{http_code}' https://mo.wooo.work/health"
|
||||
"command": "echo '<LOGIN_PASSWORD>' | sudo -S kubectl rollout restart deployment momo-app -n momo && sleep 60 && curl -s -o /dev/null -w '%{http_code}' https://mo.wooo.work/health"
|
||||
},
|
||||
"id": "repair-step-1",
|
||||
"name": "修復步驟1: 重啟 Pod",
|
||||
@@ -151,7 +151,7 @@
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "sshPassword",
|
||||
"resource": "command",
|
||||
"command": "echo '0936223270' | sudo -S kubectl rollout restart deployment coredns -n kube-system && sleep 30 && sudo kubectl delete pods -l app=momo-app -n momo --force --grace-period=0 && sleep 60 && curl -s -o /dev/null -w '%{http_code}' https://mo.wooo.work/health"
|
||||
"command": "echo '<LOGIN_PASSWORD>' | sudo -S kubectl rollout restart deployment coredns -n kube-system && sleep 30 && sudo kubectl delete pods -l app=momo-app -n momo --force --grace-period=0 && sleep 60 && curl -s -o /dev/null -w '%{http_code}' https://mo.wooo.work/health"
|
||||
},
|
||||
"id": "repair-step-2",
|
||||
"name": "修復步驟2: CoreDNS + 強制刪除 Pod",
|
||||
@@ -199,7 +199,7 @@
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "sshPassword",
|
||||
"resource": "command",
|
||||
"command": "echo '0936223270' | sudo -S kubectl scale deployment momo-app -n momo --replicas=0 && sleep 10 && sudo kubectl scale deployment momo-app -n momo --replicas=1 && sleep 90 && curl -s -o /dev/null -w '%{http_code}' https://mo.wooo.work/health"
|
||||
"command": "echo '<LOGIN_PASSWORD>' | sudo -S kubectl scale deployment momo-app -n momo --replicas=0 && sleep 10 && sudo kubectl scale deployment momo-app -n momo --replicas=1 && sleep 90 && curl -s -o /dev/null -w '%{http_code}' https://mo.wooo.work/health"
|
||||
},
|
||||
"id": "repair-step-3",
|
||||
"name": "修復步驟3: 重建 Deployment",
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}",
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
@@ -81,7 +81,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"command": "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 wooo@192.168.0.110 'echo 0936223270 | sudo -S kubectl rollout restart deployment/momo-app -n momo 2>/dev/null && echo RESTART_SUCCESS || echo RESTART_FAILED'"
|
||||
"command": "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 wooo@192.168.0.110 'echo <LOGIN_PASSWORD> | sudo -S kubectl rollout restart deployment/momo-app -n momo 2>/dev/null && echo RESTART_SUCCESS || echo RESTART_FAILED'"
|
||||
},
|
||||
"id": "restart-app",
|
||||
"name": "重啟 K8s App",
|
||||
@@ -102,7 +102,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify($json.telegramBody) }}"
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}",
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}",
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}",
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}",
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}",
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.notificationMessage }}\"\n}",
|
||||
@@ -100,7 +100,7 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.telegram.org/bot8075645931:AAH-EGKMo8ZC4QJs-Nc1_0s92xHrGdQvdpg/sendMessage",
|
||||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}",
|
||||
|
||||
@@ -19,8 +19,13 @@
|
||||
| `edm_routes.py` | EDM 與節慶儀表板 | `/edm`, `/festival` |
|
||||
| `monthly_routes.py` | 月結分析 | `/monthly_summary_analysis`, `/api/monthly_summary_data` |
|
||||
| `daily_sales_routes.py` | 當日業績 | `/daily_sales`, `/daily_sales/export*` |
|
||||
| `market_intel_routes.py` | 市場情報 Phase 26 platform seed CLI writer | `/market_intel`, `/market_intel/*`, `/api/market_intel/status`, `/api/market_intel/schema`, `/api/market_intel/schema_smoke`, `/api/market_intel/schema_db_probe`, `/api/market_intel/platform_seed_db_diff`, `/api/market_intel/adapters`, `/api/market_intel/dry_run_plan`, `/api/market_intel/discovery_plan`, `/api/market_intel/manual_discovery`, `/api/market_intel/candidate_preview`, `/api/market_intel/platform_seed_plan`, `/api/market_intel/platform_seed_write_guard`, `/api/market_intel/platform_seed_writer_plan`, `/api/market_intel/migration_blueprint`, `/api/market_intel/seed_writer_cli_status`, `/api/market_intel/write_approval_runbook`, `/api/market_intel/deployment_readiness` |
|
||||
| `market_intel_routes.py` | 市場情報 Phase 104 主路由與基礎 preview API | `/market_intel`, `/market_intel/*`, `/api/market_intel/status`, `/api/market_intel/schema`, `/api/market_intel/schema_smoke`, `/api/market_intel/schema_db_probe`, `/api/market_intel/platform_seed_db_diff`, `/api/market_intel/legacy_source_bridge`, `/api/market_intel/mcp_readiness`, `/api/market_intel/mcp_tool_contract`, `/api/market_intel/mcp_deploy_preflight`, `/api/market_intel/mcp_activation_runbook`, `/api/market_intel/mcp_fetch_gate`, `/api/market_intel/scheduler_plan`, `/api/market_intel/manual_sample_plan`, `/api/market_intel/manual_sample_acceptance`, `/api/market_intel/manual_sample_review`, `/api/market_intel/manual_sample_review/evaluate`, `/api/market_intel/manual_sample_review/candidate_handoff`, `/api/market_intel/manual_sample_review/candidate_queue_draft`, `/api/market_intel/manual_sample_review/candidate_queue_approval`, `/api/market_intel/manual_sample_review/candidate_queue_transaction`, `/api/market_intel/manual_sample_review/candidate_queue_writer_status`, `/api/market_intel/manual_sample_review/candidate_queue_writer_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_writer_postwrite_smoke`, `/api/market_intel/manual_sample_review/candidate_queue_writer_operator_drill`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_handoff`, `/api/market_intel/match_review_plan`, `/api/market_intel/opportunity_plan`, `/api/market_intel/opportunity_scoring_plan`, `/api/market_intel/opportunity_evidence_plan`, `/api/market_intel/opportunity_alert_plan`, `/api/market_intel/adapters`, `/api/market_intel/dry_run_plan`, `/api/market_intel/discovery_plan`, `/api/market_intel/manual_discovery`, `/api/market_intel/candidate_preview`, `/api/market_intel/platform_seed_plan`, `/api/market_intel/platform_seed_write_guard`, `/api/market_intel/platform_seed_writer_plan`, `/api/market_intel/migration_blueprint`, `/api/market_intel/migration_apply_drill`, `/api/market_intel/migration_catalog_review`, `/api/market_intel/migration_live_smoke`, `/api/market_intel/live_db_inventory`, `/api/market_intel/seed_writer_cli_status`, `/api/market_intel/write_approval_runbook`, `/api/market_intel/deployment_readiness` |
|
||||
| `market_intel_review_routes.py` | 市場情報人工 queue review 只讀延伸 API | `/api/market_intel/manual_sample_review/candidate_queue_review_inventory`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_approval`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_transaction`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_status`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_postwrite_smoke`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_operator_drill`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_closeout` |
|
||||
| `market_intel_review_post_routes.py` | 市場情報 review_state closeout 後只讀延伸 API(掛在 `market_intel_review_bp`) | `/api/market_intel/manual_sample_review/candidate_queue_review_decision_post_closeout_inventory`, `/api/market_intel/manual_sample_review/candidate_queue_review_completion_archive`, `/api/market_intel/manual_sample_review/candidate_queue_review_archive_summary`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_output_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_transaction`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_writer_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_package` |
|
||||
| `market_intel_review_post_ai_routes.py` | 市場情報 AI summary persistence / Telegram dispatch 後續只讀延伸 API(掛在 `market_intel_review_bp`) | `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive_summary` |
|
||||
| `market_intel_review_report_routes.py` | 市場情報 report input / report run package / report run readiness / report run receipt / report closeout / report archive / report catalog handoff 後續只讀延伸 API(掛在 `market_intel_review_bp`) | `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_input`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive_summary`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_handoff` |
|
||||
| `api_routes.py` | 通用任務與查詢 API | `/api/run_task`, `/api/history/*` |
|
||||
| `ai_routes.py` | AI 推薦、競情儀表板與 PChome 成長作戰 API | `/ai_recommend`, `/ai_intelligence`, `/api/ai/status`, `/api/ai/icaim/dashboard`, `/api/ai/pchome-growth/opportunities`, `/api/ai/pchome-growth/backfill-momo-candidates`, `/api/ai/pchome-growth/source-contract`, `/api/ai/pchome-growth/external-offers/csv-dry-run` |
|
||||
| `export_routes.py` | 匯出功能 | `/api/export/*` |
|
||||
| `import_routes.py` | 匯入功能 | `/api/import_excel`, `/api/import/monthly_summary` |
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1230
routes/ai_routes.py
1230
routes/ai_routes.py
File diff suppressed because it is too large
Load Diff
@@ -334,6 +334,8 @@ def _build_price_history_payload(session, product):
|
||||
WHERE sku = :sku
|
||||
AND source = 'pchome'
|
||||
AND crawled_at >= :start_date
|
||||
AND COALESCE(match_score, 0) >= 0.76
|
||||
AND COALESCE(tags, '[]'::jsonb) ? 'identity_v2'
|
||||
ORDER BY crawled_at
|
||||
"""), {
|
||||
"sku": str(product.i_code),
|
||||
|
||||
@@ -7,7 +7,6 @@ Bot API 路由模組
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from functools import wraps
|
||||
from flask import Blueprint, request, jsonify
|
||||
@@ -755,7 +754,7 @@ def price_decision_notify():
|
||||
if not token:
|
||||
return jsonify({'success': False, 'error': 'TELEGRAM_BOT_TOKEN not configured'}), 500
|
||||
|
||||
from services.telegram_templates import price_decision
|
||||
from services.telegram_templates import price_decision, send_telegram_with_result
|
||||
message, keyboard = price_decision(
|
||||
product_name=product_name,
|
||||
product_sku=product_sku,
|
||||
@@ -779,21 +778,16 @@ def price_decision_notify():
|
||||
sys_log.error(f"[BotAPI] price_decision_notify DB error: {e}")
|
||||
return jsonify({'success': False, 'error': f'DB error: {e}'}), 500
|
||||
|
||||
tg_url = f"https://api.telegram.org/bot{token}/sendMessage"
|
||||
for row in rows:
|
||||
try:
|
||||
resp = requests.post(tg_url, json={
|
||||
"chat_id": row[0],
|
||||
"text": message,
|
||||
"parse_mode": "HTML",
|
||||
"reply_markup": keyboard,
|
||||
}, timeout=10)
|
||||
if resp.ok:
|
||||
sent_count += 1
|
||||
else:
|
||||
errors.append(f"chat_id={row[0]}: {resp.text[:120]}")
|
||||
except Exception as e:
|
||||
errors.append(f"chat_id={row[0]}: {e}")
|
||||
admin_chat_ids = [row[0] for row in rows]
|
||||
if admin_chat_ids:
|
||||
result = send_telegram_with_result(
|
||||
message,
|
||||
chat_ids=admin_chat_ids,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
sent_count = int(result.get("sent", 0))
|
||||
errors = list(result.get("errors", []))
|
||||
|
||||
sys_log.info(f"[BotAPI] price_decision_notify sent={sent_count}/{len(rows)} insight_id={insight_id}")
|
||||
return jsonify({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# =============================================================================
|
||||
# WOOO TECH - Momo Pro System
|
||||
# CI/CD Dashboard Routes
|
||||
# 部署監控路由
|
||||
# =============================================================================
|
||||
|
||||
from flask import Blueprint, jsonify, render_template, request
|
||||
@@ -8,10 +8,12 @@ import requests
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
cicd_bp = Blueprint('cicd', __name__)
|
||||
cicd_log = logging.getLogger('cicd_routes')
|
||||
|
||||
# =============================================================================
|
||||
# 錯誤分類與修復建議
|
||||
@@ -84,12 +86,14 @@ def analyze_error(text):
|
||||
GITLAB_URL = os.environ.get('GITLAB_URL', 'http://192.168.0.110:8929')
|
||||
GITLAB_TOKEN = os.environ.get('GITLAB_TOKEN', '')
|
||||
GITLAB_PROJECT_ID = os.environ.get('GITLAB_PROJECT_ID', '1')
|
||||
GITLAB_ENABLED = (
|
||||
os.environ.get('GITLAB_ENABLED', 'false').lower() in {'1', 'true', 'yes', 'on'}
|
||||
and bool(GITLAB_TOKEN)
|
||||
)
|
||||
|
||||
if not GITLAB_TOKEN:
|
||||
import logging
|
||||
logging.getLogger('cicd_routes').warning(
|
||||
'[SECURITY] GITLAB_TOKEN is not set. GitLab API calls will fail. '
|
||||
'Set the GITLAB_TOKEN environment variable.'
|
||||
if not GITLAB_ENABLED:
|
||||
cicd_log.info(
|
||||
'[CI/CD] GitLab legacy API disabled; set GITLAB_ENABLED=true and GITLAB_TOKEN to query legacy pipelines.'
|
||||
)
|
||||
|
||||
# 環境配置
|
||||
@@ -115,12 +119,12 @@ ENVIRONMENTS = {
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Dashboard 頁面
|
||||
# 部署監控頁面
|
||||
# =============================================================================
|
||||
|
||||
@cicd_bp.route('/cicd')
|
||||
def cicd_dashboard():
|
||||
"""CI/CD Dashboard 主頁面"""
|
||||
"""部署監控主頁面"""
|
||||
return render_template('cicd_dashboard.html', active_page='cicd')
|
||||
|
||||
# =============================================================================
|
||||
@@ -136,14 +140,14 @@ def get_cicd_status():
|
||||
latest_pipeline = pipelines[0] if pipelines else None
|
||||
environments = get_all_environments_status()
|
||||
|
||||
# 取得最新 Pipeline 的詳細 Job 信息
|
||||
# 取得最新部署流程的詳細工作項目資訊
|
||||
latest_jobs = []
|
||||
failed_jobs = []
|
||||
if latest_pipeline:
|
||||
latest_jobs = get_pipeline_jobs(latest_pipeline['id'])
|
||||
failed_jobs = [j for j in latest_jobs if j.get('status') == 'failed']
|
||||
|
||||
# 如果最新 Pipeline 失敗且有未通知的失敗 Job,發送告警
|
||||
# 如果最新部署流程失敗且有未通知的失敗工作項目,發送告警
|
||||
if latest_pipeline.get('status') == 'failed' and failed_jobs:
|
||||
# 使用緩存避免重複通知
|
||||
cache_key = f"pipeline_alert_{latest_pipeline['id']}"
|
||||
@@ -449,7 +453,7 @@ def run_diagnosis(env):
|
||||
|
||||
# EwoooC 已撤除舊叢集 runtime,這裡只保留現行 Docker Compose 狀態說明。
|
||||
diagnosis['checks'].append({
|
||||
'name': 'Runtime 狀態',
|
||||
'name': '執行環境狀態',
|
||||
'status': 'ok',
|
||||
'runtime': 'Docker Compose on 192.168.0.188',
|
||||
'details': '舊叢集探測已停用;容器狀態請依 DevOps 手冊在 188 查 docker compose / /health。'
|
||||
@@ -550,19 +554,19 @@ def send_telegram_message(message):
|
||||
|
||||
|
||||
def send_pipeline_failure_alert(pipeline, failed_jobs):
|
||||
"""Pipeline 失敗時發送告警"""
|
||||
"""部署流程失敗時發送告警"""
|
||||
job_details = '\n'.join([
|
||||
f" • {j['name']}: {j.get('failure_reason', '未知原因')}"
|
||||
for j in failed_jobs
|
||||
])
|
||||
|
||||
message = f"""🚨 *CI/CD Pipeline 失敗*
|
||||
message = f"""🚨 *部署流程失敗*
|
||||
|
||||
📌 *Pipeline:* #{pipeline.get('id')}
|
||||
📌 *部署編號:* #{pipeline.get('id')}
|
||||
🌿 *分支:* `{pipeline.get('ref')}`
|
||||
📝 *Commit:* `{pipeline.get('sha', '')[:8]}`
|
||||
📝 *提交:* `{pipeline.get('sha', '')[:8]}`
|
||||
|
||||
❌ *失敗 Jobs:*
|
||||
❌ *失敗工作項目:*
|
||||
{job_details}
|
||||
|
||||
🔗 [查看詳情]({pipeline.get('web_url')})
|
||||
@@ -585,7 +589,7 @@ def send_fix_notification(env, action, results):
|
||||
for r in results
|
||||
])
|
||||
|
||||
message = f"""{status_emoji} *CI/CD 自動修復執行完成*
|
||||
message = f"""{status_emoji} *部署監控自動修復執行完成*
|
||||
|
||||
{env_icon} *環境:* {env_name}
|
||||
🔧 *動作:* {action}
|
||||
@@ -604,6 +608,9 @@ def send_fix_notification(env, action, results):
|
||||
|
||||
def gitlab_api(endpoint, method='GET', data=None):
|
||||
"""呼叫 GitLab API(支援 SSH 備用方案)"""
|
||||
if not GITLAB_ENABLED:
|
||||
return None
|
||||
|
||||
url = f"{GITLAB_URL}/api/v4{endpoint}"
|
||||
headers = {'PRIVATE-TOKEN': GITLAB_TOKEN}
|
||||
|
||||
@@ -616,7 +623,7 @@ def gitlab_api(endpoint, method='GET', data=None):
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"GitLab API Error (direct): {e}")
|
||||
cicd_log.warning("[CI/CD] GitLab direct API unavailable: %s", e)
|
||||
# 備用方案:透過 SSH 在主機上執行 curl
|
||||
return gitlab_api_via_ssh(endpoint, method, data)
|
||||
|
||||
@@ -628,6 +635,9 @@ def gitlab_api_via_ssh(endpoint, method='GET', data=None):
|
||||
Security: curl 參數以 list 形式傳給 subprocess,避免 shell injection。
|
||||
endpoint 和 json_data 均作為獨立 argv 傳入,不經過 shell 解析。
|
||||
"""
|
||||
if not GITLAB_ENABLED:
|
||||
return None
|
||||
|
||||
try:
|
||||
# 使用本地 GitLab URL;endpoint 由 gitlab_api() 內部構造,不含外部輸入
|
||||
url = f"http://127.0.0.1:8929/api/v4{endpoint}"
|
||||
@@ -665,10 +675,10 @@ def gitlab_api_via_ssh(endpoint, method='GET', data=None):
|
||||
if result.returncode == 0 and result.stdout:
|
||||
return json.loads(result.stdout)
|
||||
else:
|
||||
print(f"GitLab API Error (SSH): {result.stderr}")
|
||||
cicd_log.warning("[CI/CD] GitLab SSH API unavailable: %s", result.stderr)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"GitLab API Error (SSH fallback): {e}")
|
||||
cicd_log.warning("[CI/CD] GitLab SSH fallback failed: %s", e)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import os
|
||||
import pickle
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from urllib.parse import quote
|
||||
from flask import Blueprint, request, render_template, send_file
|
||||
from flask import Blueprint, request, render_template, send_file, url_for
|
||||
from auth import login_required
|
||||
from sqlalchemy import inspect, text
|
||||
import pandas as pd
|
||||
@@ -26,6 +26,7 @@ from services.daily_sales_service import (
|
||||
prepare_calendar_data,
|
||||
prepare_marketing_summary,
|
||||
)
|
||||
from services.competitor_intel_repository import build_competitor_intel_payload
|
||||
from services.cache_manager import (
|
||||
_DAILY_SALES_PROCESSED_CACHE as _SALES_PROCESSED_CACHE,
|
||||
_DAILY_SALES_VIEW_CACHE_DIR,
|
||||
@@ -46,6 +47,7 @@ _VIEW_CACHE_EXPIRY_SECONDS = 120
|
||||
_SHARED_VIEW_CACHE_EXPIRY_SECONDS = 1800
|
||||
_DAILY_SALES_VIEW_CACHE = {}
|
||||
_DAILY_SALES_VIEW_CACHE_MAX = 24
|
||||
_CATEGORY_TABLE_DEFAULT_LIMIT = 120
|
||||
|
||||
|
||||
def _get_daily_view_cache(cache_key):
|
||||
@@ -112,6 +114,15 @@ def _set_shared_daily_view_cache(cache_key, context):
|
||||
pass
|
||||
|
||||
|
||||
def _daily_sales_url_with_detail(detail_value):
|
||||
args = request.args.to_dict(flat=True)
|
||||
if detail_value == 'all':
|
||||
args['detail'] = 'all'
|
||||
else:
|
||||
args.pop('detail', None)
|
||||
return url_for('daily_sales.daily_sales', **args)
|
||||
|
||||
|
||||
def clear_daily_sales_cache():
|
||||
"""清除當日業績緩存(供匯入服務調用)"""
|
||||
_clear_daily_sales_cache()
|
||||
@@ -137,7 +148,7 @@ def _get_data_fingerprint(engine, table_name='daily_sales_snapshot'):
|
||||
try:
|
||||
validate_table_name(table_name)
|
||||
if engine.dialect.name == 'postgresql':
|
||||
fingerprint_sql = f'SELECT MAX(snapshot_date)::text, COUNT(*) FROM "{table_name}"'
|
||||
fingerprint_sql = f'SELECT MAX("snapshot_date"::date)::text, COUNT(*) FROM "{table_name}"'
|
||||
else:
|
||||
fingerprint_sql = f'SELECT CAST(MAX(snapshot_date) AS TEXT), COUNT(*) FROM "{table_name}"'
|
||||
with engine.connect() as conn:
|
||||
@@ -166,18 +177,32 @@ def _is_cache_valid(cache_key, engine=None, table_name='daily_sales_snapshot'):
|
||||
return True
|
||||
|
||||
|
||||
def _snapshot_date_expr(engine):
|
||||
if engine.dialect.name == 'postgresql':
|
||||
return '"snapshot_date"::date'
|
||||
return 'date("snapshot_date")'
|
||||
|
||||
|
||||
def _snapshot_date_window_clause(engine):
|
||||
date_expr = _snapshot_date_expr(engine)
|
||||
if engine.dialect.name == 'postgresql':
|
||||
return f'{date_expr} >= CAST(:start_date AS date) AND {date_expr} <= CAST(:end_date AS date)'
|
||||
return f'{date_expr} >= date(:start_date) AND {date_expr} <= date(:end_date)'
|
||||
|
||||
|
||||
def _date_param(value):
|
||||
return pd.to_datetime(value).strftime('%Y-%m-%d')
|
||||
|
||||
|
||||
def _get_available_daily_dates(engine, table_name='daily_sales_snapshot'):
|
||||
"""取得可選日期清單,避免為了 date selector 載入整張業績表。"""
|
||||
validate_table_name(table_name)
|
||||
if engine.dialect.name == 'postgresql':
|
||||
date_expr = 'snapshot_date::date'
|
||||
else:
|
||||
date_expr = 'date(snapshot_date)'
|
||||
date_expr = _snapshot_date_expr(engine)
|
||||
|
||||
query = text(
|
||||
f'SELECT DISTINCT {date_expr} AS snapshot_date '
|
||||
f'FROM "{table_name}" '
|
||||
'WHERE snapshot_date IS NOT NULL '
|
||||
'WHERE "snapshot_date" IS NOT NULL '
|
||||
'ORDER BY snapshot_date DESC'
|
||||
)
|
||||
with engine.connect() as conn:
|
||||
@@ -192,15 +217,59 @@ def _get_available_daily_dates(engine, table_name='daily_sales_snapshot'):
|
||||
return dates
|
||||
|
||||
|
||||
def _get_daily_sales_metadata(engine, table_name='daily_sales_snapshot'):
|
||||
"""一次取得日期選單與資料指紋,避免首屏每次掃兩輪 daily snapshot。"""
|
||||
validate_table_name(table_name)
|
||||
if engine.dialect.name != 'postgresql':
|
||||
return (
|
||||
_get_available_daily_dates(engine, table_name),
|
||||
_get_data_fingerprint(engine, table_name),
|
||||
)
|
||||
|
||||
query = text(
|
||||
f'SELECT MAX("snapshot_date"::date)::text AS max_snapshot_date, '
|
||||
f'COUNT(*) AS row_count, '
|
||||
f'ARRAY_AGG(DISTINCT "snapshot_date"::date ORDER BY "snapshot_date"::date DESC) '
|
||||
f'FILTER (WHERE "snapshot_date" IS NOT NULL) AS available_dates '
|
||||
f'FROM "{table_name}"'
|
||||
)
|
||||
try:
|
||||
with engine.connect() as conn:
|
||||
row = conn.execute(query).fetchone()
|
||||
if not row:
|
||||
return [], (None, 0)
|
||||
raw_dates = row[2] or []
|
||||
dates = []
|
||||
for raw_date in raw_dates:
|
||||
try:
|
||||
dates.append(pd.to_datetime(raw_date).normalize())
|
||||
except Exception:
|
||||
continue
|
||||
return dates, (row[0], row[1] or 0)
|
||||
except Exception as exc:
|
||||
sys_log.warning(f"[DailySales] metadata 查詢失敗,改用相容查詢: {exc}")
|
||||
return (
|
||||
_get_available_daily_dates(engine, table_name),
|
||||
_get_data_fingerprint(engine, table_name),
|
||||
)
|
||||
|
||||
|
||||
def _read_daily_sales_window(engine, table_name, start_date, end_date):
|
||||
"""只讀取畫面需要的日期窗口,降低冷 worker 首頁等待時間。"""
|
||||
return safe_read_sql(
|
||||
table_name,
|
||||
engine=engine,
|
||||
where_clause='"snapshot_date" >= :start_date AND "snapshot_date" <= :end_date',
|
||||
table_name = validate_table_name(table_name)
|
||||
date_expr = _snapshot_date_expr(engine)
|
||||
where_clause = _snapshot_date_window_clause(engine)
|
||||
query = text(
|
||||
f'SELECT * FROM "{table_name}" '
|
||||
f'WHERE {where_clause} '
|
||||
f'ORDER BY {date_expr} ASC'
|
||||
)
|
||||
return pd.read_sql(
|
||||
query,
|
||||
engine,
|
||||
params={
|
||||
'start_date': start_date.strftime('%Y-%m-%d'),
|
||||
'end_date': end_date.strftime('%Y-%m-%d'),
|
||||
'start_date': _date_param(start_date),
|
||||
'end_date': _date_param(end_date),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -338,6 +407,13 @@ def prepare_daily_charts(df, selected_date, days=30):
|
||||
else:
|
||||
daily_agg['avg_price'] = 0
|
||||
|
||||
if col_amount and col_amount in daily_agg.columns:
|
||||
daily_agg['margin_rate'] = (
|
||||
daily_agg['profit'] / daily_agg[col_amount].replace(0, pd.NA) * 100
|
||||
).replace([float('inf'), float('-inf')], 0).fillna(0)
|
||||
else:
|
||||
daily_agg['margin_rate'] = 0
|
||||
|
||||
# DoD
|
||||
if col_amount and col_amount in daily_agg.columns:
|
||||
daily_agg['dod_revenue'] = daily_agg[col_amount].pct_change() * 100
|
||||
@@ -379,6 +455,7 @@ def prepare_daily_charts(df, selected_date, days=30):
|
||||
'revenue': daily_agg[col_amount].tolist() if col_amount and col_amount in daily_agg.columns and not daily_agg.empty else [],
|
||||
'cost': daily_agg[col_cost].tolist() if col_cost and col_cost in daily_agg.columns and not daily_agg.empty else [],
|
||||
'profit': daily_agg['profit'].tolist() if 'profit' in daily_agg.columns and not daily_agg.empty else [],
|
||||
'margin_rate': daily_agg['margin_rate'].tolist() if 'margin_rate' in daily_agg.columns and not daily_agg.empty else [],
|
||||
'qty': daily_agg[col_qty].tolist() if col_qty and col_qty in daily_agg.columns and not daily_agg.empty else [],
|
||||
'avg_price': daily_agg['avg_price'].tolist() if 'avg_price' in daily_agg.columns and not daily_agg.empty else [],
|
||||
'dod_revenue': daily_agg['dod_revenue'].fillna(0).tolist() if 'dod_revenue' in daily_agg.columns and not daily_agg.empty else [],
|
||||
@@ -462,10 +539,58 @@ def prepare_category_summary(df, date_str=None, is_month_view=False, month_start
|
||||
|
||||
if 'profit' not in category_df.columns:
|
||||
category_df['profit'] = 0
|
||||
if 'revenue' in category_df.columns:
|
||||
category_df = category_df.sort_values(by='revenue', ascending=False)
|
||||
|
||||
return category_df.to_dict('records')
|
||||
|
||||
|
||||
def prepare_category_chart(category_records, limit=12):
|
||||
"""將分類明細壓成圖表需要的前 N 分類,避免前端再做重資料整理。"""
|
||||
if not category_records:
|
||||
return {
|
||||
'labels': [],
|
||||
'revenue': [],
|
||||
'profit': [],
|
||||
'margin_rate': [],
|
||||
'qty': [],
|
||||
}
|
||||
|
||||
category_df = pd.DataFrame(category_records)
|
||||
if category_df.empty or 'category' not in category_df.columns:
|
||||
return {
|
||||
'labels': [],
|
||||
'revenue': [],
|
||||
'profit': [],
|
||||
'margin_rate': [],
|
||||
'qty': [],
|
||||
}
|
||||
|
||||
for column in ['revenue', 'profit', 'qty']:
|
||||
if column not in category_df.columns:
|
||||
category_df[column] = 0
|
||||
category_df[column] = pd.to_numeric(category_df[column], errors='coerce').fillna(0)
|
||||
|
||||
grouped = (
|
||||
category_df.groupby('category', dropna=False)
|
||||
.agg({'revenue': 'sum', 'profit': 'sum', 'qty': 'sum'})
|
||||
.reset_index()
|
||||
.sort_values(by='revenue', ascending=False)
|
||||
.head(limit)
|
||||
)
|
||||
grouped['margin_rate'] = (
|
||||
grouped['profit'] / grouped['revenue'].replace(0, pd.NA) * 100
|
||||
).replace([float('inf'), float('-inf')], 0).fillna(0)
|
||||
|
||||
return {
|
||||
'labels': grouped['category'].fillna('未分類').astype(str).tolist(),
|
||||
'revenue': grouped['revenue'].tolist(),
|
||||
'profit': grouped['profit'].tolist(),
|
||||
'margin_rate': grouped['margin_rate'].tolist(),
|
||||
'qty': grouped['qty'].tolist(),
|
||||
}
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 頁面路由
|
||||
# ==========================================
|
||||
@@ -487,14 +612,16 @@ def daily_sales():
|
||||
error="尚未匯入當日業績資料,請先至系統設定頁面匯入 Excel。",
|
||||
selected_date=None, available_dates=[], current=None, dod=None, wow=None,
|
||||
chart_data=None, categories=None, calendar_data=None, selected_month=None,
|
||||
category_chart=None,
|
||||
datetime_now=datetime_now_str, active_page='daily_sales')
|
||||
|
||||
available_dates = _get_available_daily_dates(engine, table_name)
|
||||
available_dates, current_fingerprint = _get_daily_sales_metadata(engine, table_name)
|
||||
if not available_dates:
|
||||
return render_template('daily_sales.html',
|
||||
error="資料表為空,請先匯入當日業績資料。",
|
||||
selected_date=None, available_dates=[], current=None, dod=None, wow=None,
|
||||
chart_data=None, categories=None, calendar_data=None, selected_month=None,
|
||||
category_chart=None,
|
||||
datetime_now=datetime_now_str, active_page='daily_sales')
|
||||
|
||||
available_dates_str = [d.strftime('%Y-%m-%d') for d in available_dates]
|
||||
@@ -514,6 +641,7 @@ def daily_sales():
|
||||
is_month_view = not selected_date_param and not request.args.get('month')
|
||||
if selected_month_param and not selected_date_param:
|
||||
is_month_view = True
|
||||
show_all_categories = request.args.get('detail') == 'all'
|
||||
|
||||
month_start = selected_month.replace(day=1)
|
||||
month_end = (month_start + pd.DateOffset(months=1)) - pd.Timedelta(days=1)
|
||||
@@ -524,13 +652,13 @@ def daily_sales():
|
||||
)
|
||||
data_end = max(selected_date, month_end)
|
||||
|
||||
current_fingerprint = _get_data_fingerprint(engine, table_name)
|
||||
view_cache_key = "|".join([
|
||||
table_name,
|
||||
'view',
|
||||
selected_date.strftime('%Y-%m-%d'),
|
||||
selected_month.strftime('%Y-%m'),
|
||||
'month' if is_month_view else 'day',
|
||||
'category_all' if show_all_categories else f'category_top{_CATEGORY_TABLE_DEFAULT_LIMIT}',
|
||||
str(current_fingerprint),
|
||||
])
|
||||
cached_context = _get_daily_view_cache(view_cache_key) or _get_shared_daily_view_cache(view_cache_key)
|
||||
@@ -550,6 +678,7 @@ def daily_sales():
|
||||
error="所選日期區間沒有業績資料。",
|
||||
selected_date=None, available_dates=available_dates_str, current=None, dod=None, wow=None,
|
||||
chart_data=None, categories=None, calendar_data=None, selected_month=None,
|
||||
category_chart=None,
|
||||
datetime_now=datetime_now_str, active_page='daily_sales')
|
||||
|
||||
df = preprocess_daily_sales_data(df)
|
||||
@@ -594,6 +723,11 @@ def daily_sales():
|
||||
month_kpi['avg_price'] = 0
|
||||
|
||||
chart_data = prepare_daily_charts(df, selected_date, days=30)
|
||||
try:
|
||||
competitor_intel = build_competitor_intel_payload(engine, days=30)
|
||||
except Exception as exc:
|
||||
sys_log.warning(f"[DailySales] PChome 競價情報讀取失敗,略過圖表串接: {exc}")
|
||||
competitor_intel = None
|
||||
category_list = prepare_category_summary(
|
||||
df,
|
||||
date_str=selected_date,
|
||||
@@ -601,6 +735,12 @@ def daily_sales():
|
||||
month_start=month_start if is_month_view else None,
|
||||
month_end=month_end if is_month_view else None
|
||||
)
|
||||
category_chart = prepare_category_chart(category_list, limit=12)
|
||||
category_total_count = len(category_list)
|
||||
if show_all_categories:
|
||||
visible_categories = category_list
|
||||
else:
|
||||
visible_categories = category_list[:_CATEGORY_TABLE_DEFAULT_LIMIT]
|
||||
calendar_data = prepare_calendar_data(df, selected_month)
|
||||
marketing_data = prepare_marketing_summary(
|
||||
df,
|
||||
@@ -619,7 +759,15 @@ def daily_sales():
|
||||
'month_kpi': month_kpi,
|
||||
'is_month_view': is_month_view,
|
||||
'chart_data': chart_data,
|
||||
'categories': category_list,
|
||||
'competitor_intel': competitor_intel,
|
||||
'categories': visible_categories,
|
||||
'category_chart': category_chart,
|
||||
'category_total_count': category_total_count,
|
||||
'category_visible_count': len(visible_categories),
|
||||
'category_table_limit': _CATEGORY_TABLE_DEFAULT_LIMIT,
|
||||
'category_show_all': show_all_categories,
|
||||
'category_show_all_url': _daily_sales_url_with_detail('all'),
|
||||
'category_limited_url': _daily_sales_url_with_detail(None),
|
||||
'calendar_data': calendar_data,
|
||||
'marketing_data': marketing_data,
|
||||
'selected_month': selected_month.strftime('%Y-%m') if isinstance(selected_month, pd.Timestamp) else selected_month,
|
||||
@@ -645,6 +793,7 @@ def daily_sales():
|
||||
is_month_view=False,
|
||||
chart_data=None,
|
||||
categories=None,
|
||||
category_chart=None,
|
||||
calendar_data=None,
|
||||
marketing_data=None,
|
||||
selected_month=None,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,12 +6,16 @@ EDM 與節慶促銷路由模組
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import math
|
||||
import os
|
||||
import pickle
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from pathlib import Path
|
||||
from flask import Blueprint, request, render_template, url_for
|
||||
from auth import login_required
|
||||
from sqlalchemy import func, desc
|
||||
|
||||
from config import BASE_DIR, public_url, DATABASE_TYPE
|
||||
from config import BASE_DIR, public_url, DATABASE_TYPE, SYSTEM_VERSION
|
||||
from database.manager import DatabaseManager
|
||||
from database.models import Product
|
||||
from database.edm_models import PromoProduct
|
||||
@@ -29,6 +33,9 @@ edm_bp = Blueprint('edm', __name__)
|
||||
|
||||
_PROMO_DASHBOARD_CACHE = {}
|
||||
_PROMO_DASHBOARD_CACHE_MAX = 32
|
||||
_PROMO_PAGE_SIZE_DEFAULT = 24
|
||||
_PROMO_PAGE_SIZE_MAX = 200
|
||||
_PROMO_SHARED_CACHE_FILE = Path(BASE_DIR) / 'data' / 'promo_dashboard_cache.pkl'
|
||||
|
||||
|
||||
# ==========================================
|
||||
@@ -100,6 +107,97 @@ def _remember_promo_dashboard_data(cache_key, data):
|
||||
_PROMO_DASHBOARD_CACHE[cache_key] = data
|
||||
|
||||
|
||||
def _load_shared_promo_dashboard_cache(cache_key):
|
||||
"""讀取跨 worker 促銷 dashboard 快取,讓冷 worker 不必重算首屏資料。"""
|
||||
try:
|
||||
if not os.path.exists(_PROMO_SHARED_CACHE_FILE):
|
||||
return None
|
||||
with open(_PROMO_SHARED_CACHE_FILE, 'rb') as f:
|
||||
payload = pickle.load(f)
|
||||
if payload.get('version') != SYSTEM_VERSION:
|
||||
return None
|
||||
entries = payload.get('entries')
|
||||
if not isinstance(entries, dict):
|
||||
return None
|
||||
return entries.get(cache_key)
|
||||
except Exception as exc:
|
||||
_discard_shared_promo_dashboard_cache("load_corrupt", exc)
|
||||
return None
|
||||
|
||||
|
||||
def _discard_shared_promo_dashboard_cache(reason, exc=None):
|
||||
"""刪除損毀的跨 worker 快取,避免每個 worker 重複噴 traceback。"""
|
||||
try:
|
||||
os.remove(_PROMO_SHARED_CACHE_FILE)
|
||||
sys_log.info("promo dashboard shared cache discarded | reason=%s | error=%s", reason, exc)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
except OSError:
|
||||
sys_log.debug("promo dashboard shared cache discard failed", exc_info=True)
|
||||
|
||||
|
||||
def _write_shared_promo_dashboard_cache(cache_key, data):
|
||||
"""原子寫入促銷 dashboard 共享快取;失敗不阻斷頁面回應。"""
|
||||
cache_file = str(_PROMO_SHARED_CACHE_FILE)
|
||||
tmp_file = f"{cache_file}.{os.getpid()}.tmp"
|
||||
entries = {}
|
||||
try:
|
||||
if os.path.exists(_PROMO_SHARED_CACHE_FILE):
|
||||
try:
|
||||
with open(_PROMO_SHARED_CACHE_FILE, 'rb') as f:
|
||||
payload = pickle.load(f)
|
||||
if payload.get('version') == SYSTEM_VERSION and isinstance(payload.get('entries'), dict):
|
||||
entries = payload['entries']
|
||||
except Exception as exc:
|
||||
_discard_shared_promo_dashboard_cache("write_corrupt", exc)
|
||||
entries = {}
|
||||
entries[cache_key] = data
|
||||
while len(entries) > _PROMO_DASHBOARD_CACHE_MAX:
|
||||
entries.pop(next(iter(entries)))
|
||||
|
||||
os.makedirs(os.path.dirname(cache_file), exist_ok=True)
|
||||
with open(tmp_file, 'wb') as f:
|
||||
pickle.dump({'version': SYSTEM_VERSION, 'entries': entries}, f, protocol=pickle.HIGHEST_PROTOCOL)
|
||||
os.replace(tmp_file, cache_file)
|
||||
except Exception:
|
||||
try:
|
||||
if os.path.exists(tmp_file):
|
||||
os.remove(tmp_file)
|
||||
except OSError:
|
||||
pass
|
||||
sys_log.debug("promo dashboard shared cache write failed", exc_info=True)
|
||||
|
||||
|
||||
def _get_promo_page_window_args():
|
||||
"""讀取促銷商品清單分頁參數,限制首屏 HTML 重量。"""
|
||||
page = request.args.get('page', 1, type=int) or 1
|
||||
per_page = request.args.get('per_page', _PROMO_PAGE_SIZE_DEFAULT, type=int) or _PROMO_PAGE_SIZE_DEFAULT
|
||||
return max(1, page), max(20, min(per_page, _PROMO_PAGE_SIZE_MAX))
|
||||
|
||||
|
||||
def _paginate_active_slot(data, page, per_page):
|
||||
"""只裁切目前顯示時段;其他統計仍保留完整資料。"""
|
||||
active_tab = data.get('active_tab')
|
||||
grouped_items = dict(data.get('sorted_grouped_items') or {})
|
||||
active_items = list(grouped_items.get(active_tab, []))
|
||||
total_items = len(active_items)
|
||||
total_pages = max(1, math.ceil(total_items / per_page)) if total_items else 1
|
||||
page = min(max(1, page), total_pages)
|
||||
start_idx = (page - 1) * per_page
|
||||
end_idx = min(start_idx + per_page, total_items)
|
||||
grouped_items[active_tab] = active_items[start_idx:end_idx]
|
||||
return grouped_items, {
|
||||
'page': page,
|
||||
'per_page': per_page,
|
||||
'total_pages': total_pages,
|
||||
'total_items': total_items,
|
||||
'start_item': start_idx + 1 if total_items else 0,
|
||||
'end_item': end_idx,
|
||||
'has_prev': page > 1,
|
||||
'has_next': page < total_pages,
|
||||
}
|
||||
|
||||
|
||||
def _build_promo_dashboard_data(session, page_type, page_name, sort_by, order, requested_slot=None):
|
||||
"""
|
||||
通用的促銷儀表板數據建構函數
|
||||
@@ -109,6 +207,10 @@ def _build_promo_dashboard_data(session, page_type, page_name, sort_by, order, r
|
||||
cached_data = _PROMO_DASHBOARD_CACHE.get(cache_key)
|
||||
if cached_data is not None:
|
||||
return cached_data
|
||||
cached_data = _load_shared_promo_dashboard_cache(cache_key)
|
||||
if cached_data is not None:
|
||||
_remember_promo_dashboard_data(cache_key, cached_data)
|
||||
return cached_data
|
||||
|
||||
# 1. 基礎統計
|
||||
last_update = session.query(PromoProduct.crawled_at).filter(
|
||||
@@ -341,9 +443,37 @@ def _build_promo_dashboard_data(session, page_type, page_name, sort_by, order, r
|
||||
'current_batch_id': current_batch_id
|
||||
}
|
||||
_remember_promo_dashboard_data(cache_key, data)
|
||||
_write_shared_promo_dashboard_cache(cache_key, data)
|
||||
return data
|
||||
|
||||
|
||||
def warm_promo_dashboard_cache(reason='manual'):
|
||||
"""預熱常用活動看板 shared cache,避免部署後第一個使用者承擔冷 worker。"""
|
||||
db = DatabaseManager()
|
||||
session = db.get_session()
|
||||
page_configs = [
|
||||
('edm', '限時搶購'),
|
||||
('festival', '1.1狂歡購物節'),
|
||||
('mothers_day', '母親節超值限時購'),
|
||||
('valentine_520', '520情人節限定購物'),
|
||||
('labor_day', '勞動節購物優惠'),
|
||||
]
|
||||
started = datetime.now(TAIPEI_TZ)
|
||||
warmed = 0
|
||||
try:
|
||||
for page_type, page_name in page_configs:
|
||||
data = _build_promo_dashboard_data(session, page_type, page_name, 'default', 'desc', None)
|
||||
if data:
|
||||
warmed += 1
|
||||
elapsed_ms = (datetime.now(TAIPEI_TZ) - started).total_seconds() * 1000
|
||||
sys_log.info(
|
||||
f"[EDM] [Cache] ✅ 活動看板預熱完成 | reason={reason} | pages={warmed} | 耗時={elapsed_ms:.0f}ms"
|
||||
)
|
||||
return warmed
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
# ==========================================
|
||||
# EDM 儀表板路由
|
||||
# ==========================================
|
||||
@@ -358,9 +488,11 @@ def edm_dashboard():
|
||||
sort_by = request.args.get('sort_by', 'default')
|
||||
order = request.args.get('order', 'desc')
|
||||
requested_slot = request.args.get('slot')
|
||||
page, per_page = _get_promo_page_window_args()
|
||||
|
||||
try:
|
||||
data = _build_promo_dashboard_data(session, 'edm', '限時搶購', sort_by, order, requested_slot)
|
||||
grouped_items, page_window = _paginate_active_slot(data, page, per_page)
|
||||
|
||||
# 建立儀表板頁籤
|
||||
promo_pages = [
|
||||
@@ -380,8 +512,9 @@ def edm_dashboard():
|
||||
promo_pages=promo_pages,
|
||||
current_promo_page='edm',
|
||||
page_title='MOMO 限時搶購',
|
||||
grouped_items=data['sorted_grouped_items'],
|
||||
grouped_items=grouped_items,
|
||||
slot_stats=data['slot_stats'],
|
||||
page_window=page_window,
|
||||
total_edm_products=len(data['items_in_batch']),
|
||||
last_update=data['last_update_str'],
|
||||
activity_time=data['activity_time'],
|
||||
@@ -414,9 +547,11 @@ def festival_dashboard():
|
||||
sort_by = request.args.get('sort_by', 'default')
|
||||
order = request.args.get('order', 'desc')
|
||||
requested_slot = request.args.get('slot')
|
||||
page, per_page = _get_promo_page_window_args()
|
||||
|
||||
try:
|
||||
data = _build_promo_dashboard_data(session, PAGE_TYPE, PAGE_NAME, sort_by, order, requested_slot)
|
||||
grouped_items, page_window = _paginate_active_slot(data, page, per_page)
|
||||
|
||||
# 建立儀表板頁籤
|
||||
promo_pages = [
|
||||
@@ -435,8 +570,9 @@ def festival_dashboard():
|
||||
promo_pages=promo_pages,
|
||||
current_promo_page='festival',
|
||||
page_title=PAGE_NAME,
|
||||
grouped_items=data['sorted_grouped_items'],
|
||||
grouped_items=grouped_items,
|
||||
slot_stats=data['slot_stats'],
|
||||
page_window=page_window,
|
||||
total_edm_products=len(data['items_in_batch']),
|
||||
last_update=data['last_update_str'],
|
||||
activity_time=data['activity_time'],
|
||||
@@ -467,9 +603,11 @@ def mothers_day_dashboard():
|
||||
sort_by = request.args.get('sort_by', 'default')
|
||||
order = request.args.get('order', 'desc')
|
||||
requested_slot = request.args.get('slot')
|
||||
page, per_page = _get_promo_page_window_args()
|
||||
|
||||
try:
|
||||
data = _build_promo_dashboard_data(session, PAGE_TYPE, PAGE_NAME, sort_by, order, requested_slot)
|
||||
grouped_items, page_window = _paginate_active_slot(data, page, per_page)
|
||||
|
||||
# 建立儀表板頁籤
|
||||
promo_pages = [
|
||||
@@ -488,8 +626,9 @@ def mothers_day_dashboard():
|
||||
promo_pages=promo_pages,
|
||||
current_promo_page='mothers_day',
|
||||
page_title=PAGE_NAME,
|
||||
grouped_items=data['sorted_grouped_items'],
|
||||
grouped_items=grouped_items,
|
||||
slot_stats=data['slot_stats'],
|
||||
page_window=page_window,
|
||||
total_edm_products=len(data['items_in_batch']),
|
||||
last_update=data['last_update_str'],
|
||||
activity_time=data['activity_time'],
|
||||
@@ -520,9 +659,11 @@ def valentine_520_dashboard():
|
||||
sort_by = request.args.get('sort_by', 'default')
|
||||
order = request.args.get('order', 'desc')
|
||||
requested_slot = request.args.get('slot')
|
||||
page, per_page = _get_promo_page_window_args()
|
||||
|
||||
try:
|
||||
data = _build_promo_dashboard_data(session, PAGE_TYPE, PAGE_NAME, sort_by, order, requested_slot)
|
||||
grouped_items, page_window = _paginate_active_slot(data, page, per_page)
|
||||
|
||||
# 建立儀表板頁籤
|
||||
promo_pages = [
|
||||
@@ -541,8 +682,9 @@ def valentine_520_dashboard():
|
||||
promo_pages=promo_pages,
|
||||
current_promo_page='valentine_520',
|
||||
page_title=PAGE_NAME,
|
||||
grouped_items=data['sorted_grouped_items'],
|
||||
grouped_items=grouped_items,
|
||||
slot_stats=data['slot_stats'],
|
||||
page_window=page_window,
|
||||
total_edm_products=len(data['items_in_batch']),
|
||||
last_update=data['last_update_str'],
|
||||
activity_time=data['activity_time'],
|
||||
@@ -573,9 +715,11 @@ def labor_day_dashboard():
|
||||
sort_by = request.args.get('sort_by', 'default')
|
||||
order = request.args.get('order', 'desc')
|
||||
requested_slot = request.args.get('slot')
|
||||
page, per_page = _get_promo_page_window_args()
|
||||
|
||||
try:
|
||||
data = _build_promo_dashboard_data(session, PAGE_TYPE, PAGE_NAME, sort_by, order, requested_slot)
|
||||
grouped_items, page_window = _paginate_active_slot(data, page, per_page)
|
||||
|
||||
# 建立儀表板頁籤
|
||||
promo_pages = [
|
||||
@@ -594,8 +738,9 @@ def labor_day_dashboard():
|
||||
promo_pages=promo_pages,
|
||||
current_promo_page='labor_day',
|
||||
page_title=PAGE_NAME,
|
||||
grouped_items=data['sorted_grouped_items'],
|
||||
grouped_items=grouped_items,
|
||||
slot_stats=data['slot_stats'],
|
||||
page_window=page_window,
|
||||
total_edm_products=len(data['items_in_batch']),
|
||||
last_update=data['last_update_str'],
|
||||
activity_time=data['activity_time'],
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import os
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from flask import Blueprint, request, send_file, redirect, url_for, flash
|
||||
from auth import login_required
|
||||
@@ -31,6 +32,8 @@ sys_log = SystemLogger("ExportRoutes").get_logger()
|
||||
# Blueprint 定義
|
||||
export_bp = Blueprint('export', __name__)
|
||||
|
||||
_EXCEL_ILLEGAL_CHAR_RE = re.compile(r'[\x00-\x08\x0B-\x0C\x0E-\x1F]')
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 輔助函數 (使用獨立模組,避免循環依賴)
|
||||
@@ -48,6 +51,57 @@ def _get_sales_cache():
|
||||
return _SALES_PROCESSED_CACHE
|
||||
|
||||
|
||||
def _sanitize_excel_cell(value):
|
||||
"""Remove control characters rejected by openpyxl worksheet cells."""
|
||||
if isinstance(value, str):
|
||||
return _EXCEL_ILLEGAL_CHAR_RE.sub('', value)
|
||||
return value
|
||||
|
||||
|
||||
def _sanitize_excel_dataframe(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Return an Excel-safe copy without changing numeric/date columns."""
|
||||
if df.empty:
|
||||
return df
|
||||
cleaned = df.copy()
|
||||
for column in cleaned.columns:
|
||||
if cleaned[column].dtype == object:
|
||||
cleaned[column] = cleaned[column].map(_sanitize_excel_cell)
|
||||
return cleaned
|
||||
|
||||
|
||||
def _flatten_review_decision_envelope(item):
|
||||
"""Flatten the shared review decision envelope into operator-friendly columns."""
|
||||
envelope = item.get('decision_envelope') or {}
|
||||
guardrails = envelope.get('guardrails') or {}
|
||||
recommended_action = envelope.get('recommended_action') or {}
|
||||
evidence = envelope.get('evidence') or []
|
||||
evidence_parts = []
|
||||
if isinstance(evidence, list):
|
||||
for row in evidence[:6]:
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
metric = row.get('metric') or row.get('type') or 'evidence'
|
||||
value = row.get('value')
|
||||
basis = row.get('basis') or ''
|
||||
text = f"{metric}={value}" if value not in (None, '') else str(metric)
|
||||
if basis:
|
||||
text = f"{text} ({basis})"
|
||||
evidence_parts.append(text)
|
||||
|
||||
return {
|
||||
'決策信封ID': envelope.get('decision_id') or '',
|
||||
'決策類型': envelope.get('decision_type') or '',
|
||||
'決策等級': envelope.get('severity') or '',
|
||||
'決策建議代碼': recommended_action.get('action') or '',
|
||||
'決策責任人': recommended_action.get('owner') or '',
|
||||
'需人工覆核': '是' if recommended_action.get('requires_hitl') else '否',
|
||||
'資料品質': guardrails.get('data_quality') or '',
|
||||
'自動執行允許': '是' if guardrails.get('can_auto_execute') else '否',
|
||||
'自動執行阻擋原因': guardrails.get('blocked_reason') or '',
|
||||
'決策證據摘要': ';'.join(evidence_parts),
|
||||
}
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 全分類匯出
|
||||
# ==========================================
|
||||
@@ -137,7 +191,8 @@ def export_excel_ai_picks():
|
||||
AND (cp.expires_at IS NULL OR cp.expires_at > CURRENT_TIMESTAMP)
|
||||
AND cp.price IS NOT NULL
|
||||
AND cp.price > 0
|
||||
AND COALESCE(cp.match_score, 0) >= 0.42
|
||||
AND COALESCE(cp.match_score, 0) >= 0.76
|
||||
AND COALESCE(cp.tags, '[]'::jsonb) ? 'identity_v2'
|
||||
ORDER BY cp.sku, cp.crawled_at DESC NULLS LAST
|
||||
)
|
||||
SELECT
|
||||
@@ -217,7 +272,7 @@ def export_excel_ai_picks():
|
||||
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter(output, engine='openpyxl') as writer:
|
||||
df = pd.DataFrame(export_rows)
|
||||
df = _sanitize_excel_dataframe(pd.DataFrame(export_rows))
|
||||
df.to_excel(writer, index=False, sheet_name='AI挑品清單')
|
||||
worksheet = writer.sheets['AI挑品清單']
|
||||
for column_cells in worksheet.columns:
|
||||
@@ -244,6 +299,131 @@ def export_excel_ai_picks():
|
||||
session.close()
|
||||
|
||||
|
||||
@export_bp.route('/api/export/excel/pchome-review')
|
||||
@login_required
|
||||
def export_excel_pchome_review():
|
||||
"""匯出 PChome 比價覆核隊列,保留 matcher 診斷與人工處置欄位。"""
|
||||
from services.competitor_intel_repository import (
|
||||
REVIEW_STATUS_FILTER_GROUPS,
|
||||
fetch_competitor_review_queue_page,
|
||||
)
|
||||
|
||||
db = DatabaseManager()
|
||||
session = db.get_session()
|
||||
try:
|
||||
search_query = (request.args.get('q') or request.args.get('search') or '').strip()
|
||||
category = (request.args.get('category') or '').strip()
|
||||
if category.lower() == 'all':
|
||||
category = ''
|
||||
|
||||
status_filter = (request.args.get('review_status') or request.args.get('status') or '').strip()
|
||||
if status_filter == 'all' or status_filter not in REVIEW_STATUS_FILTER_GROUPS:
|
||||
status_filter = ''
|
||||
|
||||
try:
|
||||
limit = int(request.args.get('limit') or 500)
|
||||
except (TypeError, ValueError):
|
||||
limit = 500
|
||||
limit = max(1, min(limit, 2000))
|
||||
|
||||
engine = session.get_bind()
|
||||
rows = []
|
||||
page = 1
|
||||
while len(rows) < limit:
|
||||
per_page = min(100, limit - len(rows))
|
||||
payload = fetch_competitor_review_queue_page(
|
||||
engine,
|
||||
page=page,
|
||||
per_page=per_page,
|
||||
search_query=search_query,
|
||||
category=category,
|
||||
status_filter=status_filter,
|
||||
)
|
||||
batch = payload.get('items') or []
|
||||
if not batch:
|
||||
break
|
||||
rows.extend(batch)
|
||||
if len(rows) >= int(payload.get('total') or 0) or len(batch) < per_page:
|
||||
break
|
||||
page += 1
|
||||
|
||||
if not rows:
|
||||
return "目前沒有 PChome 覆核資料可匯出", 404
|
||||
|
||||
export_rows = []
|
||||
for idx, item in enumerate(rows[:limit], start=1):
|
||||
sku = str(item.get('sku') or '').strip()
|
||||
pchome_id = str(item.get('candidate_pc_id') or '').strip()
|
||||
unit_comparison = item.get('unit_comparison') or {}
|
||||
momo_url = build_momo_product_url(sku) if sku else ''
|
||||
pchome_url = f"https://24h.pchome.com.tw/prod/{pchome_id}" if pchome_id else ''
|
||||
export_rows.append({
|
||||
'覆核序': idx,
|
||||
'狀態': item.get('status_label') or '',
|
||||
'建議處置': item.get('action_label') or '',
|
||||
'診斷原因': item.get('diagnostic_reason_text') or '',
|
||||
**_flatten_review_decision_envelope(item),
|
||||
'MOMO商品ID': sku,
|
||||
'MOMO商品名稱': item.get('name') or '',
|
||||
'分類': item.get('category') or '',
|
||||
'MOMO價格': float(item.get('momo_price') or 0),
|
||||
'候選PChome商品ID': pchome_id,
|
||||
'候選PChome商品名稱': item.get('candidate_pc_name') or '',
|
||||
'候選PChome價格': float(item.get('candidate_pc_price') or 0),
|
||||
'Match分數%': round(float(item.get('best_match_score') or 0) * 100, 1),
|
||||
'候選數': int(item.get('candidate_count') or 0),
|
||||
'單位價比較': unit_comparison.get('summary') or '',
|
||||
'原始診斷': item.get('match_diagnostic') or '',
|
||||
'嘗試時間': item.get('attempted_at') or '',
|
||||
'MOMO商品URL': momo_url,
|
||||
'PChome商品URL': pchome_url,
|
||||
})
|
||||
|
||||
output = io.BytesIO()
|
||||
with pd.ExcelWriter(output, engine='openpyxl') as writer:
|
||||
df = _sanitize_excel_dataframe(pd.DataFrame(export_rows))
|
||||
df.to_excel(writer, index=False, sheet_name='PChome覆核隊列')
|
||||
worksheet = writer.sheets['PChome覆核隊列']
|
||||
for column_cells in worksheet.columns:
|
||||
header = str(column_cells[0].value or '')
|
||||
width = min(max(len(header) + 4, 12), 42)
|
||||
if header in {
|
||||
'MOMO商品名稱',
|
||||
'候選PChome商品名稱',
|
||||
'建議處置',
|
||||
'決策信封ID',
|
||||
'決策建議代碼',
|
||||
'診斷原因',
|
||||
'自動執行阻擋原因',
|
||||
'決策證據摘要',
|
||||
'單位價比較',
|
||||
'原始診斷',
|
||||
'MOMO商品URL',
|
||||
'PChome商品URL',
|
||||
}:
|
||||
width = 52
|
||||
worksheet.column_dimensions[column_cells[0].column_letter].width = width
|
||||
worksheet.freeze_panes = 'A2'
|
||||
|
||||
output.seek(0)
|
||||
status_label = status_filter or 'all'
|
||||
filename = f"PChome比價覆核_{status_label}_{datetime.now(TAIPEI_TZ).strftime('%Y%m%d_%H%M')}.xlsx"
|
||||
sys_log.info(
|
||||
f"[Web] [Export] PChome 覆核隊列匯出成功 | rows={len(export_rows)} status={status_label}"
|
||||
)
|
||||
return send_file(
|
||||
output,
|
||||
as_attachment=True,
|
||||
download_name=filename,
|
||||
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
)
|
||||
except Exception as e:
|
||||
sys_log.error(f"[Web] [Export] PChome 覆核隊列匯出失敗 | Error: {e}")
|
||||
return f"匯出失敗: {e}", 500
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@export_bp.route('/api/export/excel/delisted')
|
||||
@login_required
|
||||
def export_excel_delisted():
|
||||
|
||||
@@ -228,6 +228,12 @@ def import_excel():
|
||||
message = '匯入完成,但所有資料皆已存在 (重複),無新增數據。'
|
||||
|
||||
clear_sales_cache_for_table(table_name)
|
||||
if table_name == 'realtime_sales_monthly':
|
||||
try:
|
||||
from services.cache_service import clear_growth_cache
|
||||
clear_growth_cache()
|
||||
except Exception as cache_error:
|
||||
sys_log.warning(f"[Web] [Cache] 成長分析快取清除失敗: {cache_error}")
|
||||
sys_log.info(f"[Web] [Cache] 已清除業績分析快取: {table_name}")
|
||||
|
||||
return jsonify({'status': 'success', 'message': message, 'rows': rows_imported, 'table': table_name})
|
||||
@@ -241,6 +247,12 @@ def import_excel():
|
||||
df.to_sql(table_name, con=engine, if_exists='replace', index=False)
|
||||
|
||||
clear_sales_cache_for_table(table_name)
|
||||
if table_name == 'realtime_sales_monthly':
|
||||
try:
|
||||
from services.cache_service import clear_growth_cache
|
||||
clear_growth_cache()
|
||||
except Exception as cache_error:
|
||||
sys_log.warning(f"[Web] [Cache] 成長分析快取清除失敗: {cache_error}")
|
||||
sys_log.info(f"[Web] [Cache] 已清除業績分析快取: {table_name}")
|
||||
|
||||
return jsonify({
|
||||
|
||||
207
routes/market_intel_mcp_review_routes.py
Normal file
207
routes/market_intel_mcp_review_routes.py
Normal file
@@ -0,0 +1,207 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""市場情報 MCP review route extension。"""
|
||||
|
||||
from flask import jsonify, request
|
||||
|
||||
from auth import login_required
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from services.market_intel import MarketIntelService
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_review_decision_approval import (
|
||||
build_mcp_fetch_candidate_queue_writer_review_decision_approval_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight import (
|
||||
build_mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_target_source_governance_review import (
|
||||
build_mcp_fetch_target_source_governance_review_preview,
|
||||
)
|
||||
from services.market_intel.mcp_professional_source_governance import (
|
||||
build_mcp_professional_source_governance_preview,
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_review_decision_approval",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_review_decision_approval():
|
||||
writer_review_decision_package = {}
|
||||
writer_review_decision_result = None
|
||||
operator_review_approval = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_review_decision_approval_package")
|
||||
or payload.get("candidate_queue_writer_review_decision_approval")
|
||||
or payload.get("review_decision_approval")
|
||||
or payload.get("operator_review_approval")
|
||||
or payload.get("operator_review_decision_approval")
|
||||
or payload
|
||||
)
|
||||
writer_review_decision_package = (
|
||||
package.get("writer_review_decision_package")
|
||||
or package.get("candidate_queue_writer_review_decision")
|
||||
or package.get("writer_review_decision")
|
||||
or package.get("review_decision_package")
|
||||
or package.get("review_decision")
|
||||
or {}
|
||||
)
|
||||
writer_review_decision_result = (
|
||||
package.get("writer_review_decision_result")
|
||||
or package.get("mcp_fetch_candidate_queue_writer_review_decision")
|
||||
)
|
||||
operator_review_approval = (
|
||||
package.get("operator_review_approval")
|
||||
or package.get("operator_review_decision_approval")
|
||||
or package.get("candidate_queue_review_decision_approval")
|
||||
or package.get("writer_review_decision_approval")
|
||||
or package.get("review_decision_approval_payload")
|
||||
or package.get("approval_payload")
|
||||
or package.get("approval")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_review_decision_approval_preview(
|
||||
writer_review_decision_package=writer_review_decision_package,
|
||||
writer_review_decision_result=writer_review_decision_result,
|
||||
operator_review_decision_approval=operator_review_approval,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight():
|
||||
writer_review_decision_approval_package = {}
|
||||
writer_review_decision_approval_result = None
|
||||
operator_writer_preflight = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_review_decision_approval_writer_preflight_package")
|
||||
or payload.get(
|
||||
"candidate_queue_writer_review_decision_approval_writer_preflight"
|
||||
)
|
||||
or payload.get("review_decision_approval_writer_preflight")
|
||||
or payload.get("operator_writer_preflight")
|
||||
or payload.get("operator_review_decision_approval_writer_preflight")
|
||||
or payload
|
||||
)
|
||||
writer_review_decision_approval_package = (
|
||||
package.get("writer_review_decision_approval_package")
|
||||
or package.get("candidate_queue_writer_review_decision_approval")
|
||||
or package.get("writer_review_decision_approval")
|
||||
or package.get("review_decision_approval_package")
|
||||
or package.get("review_decision_approval")
|
||||
or {}
|
||||
)
|
||||
writer_review_decision_approval_result = (
|
||||
package.get("writer_review_decision_approval_result")
|
||||
or package.get(
|
||||
"mcp_fetch_candidate_queue_writer_review_decision_approval"
|
||||
)
|
||||
)
|
||||
operator_writer_preflight = (
|
||||
package.get("operator_writer_preflight")
|
||||
or package.get("operator_review_decision_approval_writer_preflight")
|
||||
or package.get(
|
||||
"candidate_queue_review_decision_approval_writer_preflight"
|
||||
)
|
||||
or package.get("writer_review_decision_approval_writer_preflight")
|
||||
or package.get("writer_preflight_payload")
|
||||
or package.get("preflight_payload")
|
||||
or package.get("preflight")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight_preview(
|
||||
writer_review_decision_approval_package=(
|
||||
writer_review_decision_approval_package
|
||||
),
|
||||
writer_review_decision_approval_result=(
|
||||
writer_review_decision_approval_result
|
||||
),
|
||||
operator_review_decision_approval_writer_preflight=(
|
||||
operator_writer_preflight
|
||||
),
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_professional_source_governance",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_professional_source_governance():
|
||||
operator_source_governance = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("professional_source_governance_package")
|
||||
or payload.get("source_governance_package")
|
||||
or payload.get("operator_source_governance")
|
||||
or payload.get("market_source_governance")
|
||||
or payload
|
||||
)
|
||||
operator_source_governance = (
|
||||
package.get("operator_source_governance")
|
||||
or package.get("source_governance")
|
||||
or package
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_professional_source_governance_preview(
|
||||
operator_source_governance=operator_source_governance,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_target_source_governance_review",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_target_source_governance_review():
|
||||
professional_source_governance_package = None
|
||||
target_review_package = None
|
||||
operator_confirmations = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("fetch_target_source_governance_review_package")
|
||||
or payload.get("source_governed_target_review_package")
|
||||
or payload
|
||||
)
|
||||
professional_source_governance_package = (
|
||||
package.get("professional_source_governance_package")
|
||||
or package.get("source_governance_package")
|
||||
or package.get("operator_source_governance")
|
||||
)
|
||||
target_review_package = (
|
||||
package.get("target_review_package")
|
||||
or package.get("mcp_fetch_target_review")
|
||||
or package.get("target_review")
|
||||
)
|
||||
operator_confirmations = package.get("operator_confirmations", {})
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_target_source_governance_review_preview(
|
||||
professional_source_governance_package=professional_source_governance_package,
|
||||
target_review_package=target_review_package,
|
||||
operator_confirmations=operator_confirmations,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
770
routes/market_intel_mcp_run_routes.py
Normal file
770
routes/market_intel_mcp_run_routes.py
Normal file
@@ -0,0 +1,770 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""市場情報 MCP run package route extension。"""
|
||||
|
||||
from flask import jsonify, request
|
||||
|
||||
from auth import login_required
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from services.market_intel import MarketIntelService
|
||||
from services.market_intel.mcp_fetch_run_package import (
|
||||
build_mcp_fetch_run_package_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_run_readiness import (
|
||||
build_mcp_fetch_run_readiness_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_run_receipt import (
|
||||
build_mcp_fetch_run_receipt_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_result_parser_review import (
|
||||
build_mcp_fetch_result_parser_review_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_handoff_review import (
|
||||
build_mcp_fetch_candidate_handoff_review_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_review import (
|
||||
build_mcp_fetch_candidate_queue_review_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_preflight import (
|
||||
build_mcp_fetch_candidate_queue_writer_preflight_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_cli_review import (
|
||||
build_mcp_fetch_candidate_queue_writer_cli_review_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_run_package_review import (
|
||||
build_mcp_fetch_candidate_queue_writer_run_package_review_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_run_readiness import (
|
||||
build_mcp_fetch_candidate_queue_writer_run_readiness_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_run_receipt_review import (
|
||||
build_mcp_fetch_candidate_queue_writer_run_receipt_review_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_run_closeout_review import (
|
||||
build_mcp_fetch_candidate_queue_writer_run_closeout_review_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_post_closeout_inventory_review import (
|
||||
build_mcp_fetch_candidate_queue_writer_post_closeout_inventory_review_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_review_handoff import (
|
||||
build_mcp_fetch_candidate_queue_writer_review_handoff_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_review_inventory import (
|
||||
build_mcp_fetch_candidate_queue_writer_review_inventory_preview,
|
||||
)
|
||||
from services.market_intel.mcp_fetch_candidate_queue_writer_review_decision import (
|
||||
build_mcp_fetch_candidate_queue_writer_review_decision_preview,
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route("/api/market_intel/mcp_fetch_run_package", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_run_package():
|
||||
target_review_package = {}
|
||||
target_review_result = None
|
||||
operator_run_controls = {}
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = payload.get("run_package") or payload.get("fetch_run_package") or payload
|
||||
target_review_package = package.get("target_review_package", {})
|
||||
target_review_result = package.get("target_review_result")
|
||||
operator_run_controls = package.get("operator_run_controls", {})
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_run_package_preview(
|
||||
target_review_package=target_review_package,
|
||||
target_review_result=target_review_result,
|
||||
operator_run_controls=operator_run_controls,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route("/api/market_intel/mcp_fetch_run_readiness", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_run_readiness():
|
||||
run_package = {}
|
||||
run_package_result = None
|
||||
operator_readiness = {}
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = payload.get("run_readiness") or payload.get("readiness_package") or payload
|
||||
run_package = (
|
||||
package.get("run_package")
|
||||
or package.get("fetch_run_package")
|
||||
or package.get("run_package_payload")
|
||||
or {}
|
||||
)
|
||||
run_package_result = (
|
||||
package.get("run_package_result")
|
||||
or package.get("mcp_fetch_run_package")
|
||||
)
|
||||
operator_readiness = (
|
||||
package.get("operator_readiness")
|
||||
or package.get("operator_run_readiness")
|
||||
or {}
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_run_readiness_preview(
|
||||
run_package=run_package,
|
||||
run_package_result=run_package_result,
|
||||
operator_readiness=operator_readiness,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route("/api/market_intel/mcp_fetch_run_receipt", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_run_receipt():
|
||||
run_readiness_package = {}
|
||||
run_readiness_result = None
|
||||
manual_fetch_receipt = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = payload.get("run_receipt") or payload.get("receipt_review") or payload
|
||||
run_readiness_package = (
|
||||
package.get("run_readiness_package")
|
||||
or package.get("run_readiness")
|
||||
or package.get("readiness_package")
|
||||
or {}
|
||||
)
|
||||
run_readiness_result = (
|
||||
package.get("run_readiness_result")
|
||||
or package.get("mcp_fetch_run_readiness")
|
||||
)
|
||||
manual_fetch_receipt = (
|
||||
package.get("manual_fetch_receipt")
|
||||
or package.get("fetch_receipt")
|
||||
or package.get("receipt")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_run_receipt_preview(
|
||||
run_readiness_package=run_readiness_package,
|
||||
run_readiness_result=run_readiness_result,
|
||||
manual_fetch_receipt=manual_fetch_receipt,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_result_parser_review",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_result_parser_review():
|
||||
run_receipt_package = {}
|
||||
run_receipt_result = None
|
||||
parser_result = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("parser_review")
|
||||
or payload.get("result_parser_review")
|
||||
or payload
|
||||
)
|
||||
run_receipt_package = (
|
||||
package.get("run_receipt_package")
|
||||
or package.get("run_receipt")
|
||||
or package.get("receipt_review")
|
||||
or {}
|
||||
)
|
||||
run_receipt_result = (
|
||||
package.get("run_receipt_result")
|
||||
or package.get("mcp_fetch_run_receipt")
|
||||
)
|
||||
parser_result = (
|
||||
package.get("parser_result")
|
||||
or package.get("fetch_result_parser")
|
||||
or package.get("parsed_result")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_result_parser_review_preview(
|
||||
run_receipt_package=run_receipt_package,
|
||||
run_receipt_result=run_receipt_result,
|
||||
parser_result=parser_result,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_handoff_review",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_handoff_review():
|
||||
parser_review_package = {}
|
||||
parser_review_result = None
|
||||
candidate_handoff = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("candidate_handoff_review")
|
||||
or payload.get("handoff_review")
|
||||
or payload
|
||||
)
|
||||
parser_review_package = (
|
||||
package.get("parser_review_package")
|
||||
or package.get("parser_review")
|
||||
or package.get("result_parser_review")
|
||||
or {}
|
||||
)
|
||||
parser_review_result = (
|
||||
package.get("parser_review_result")
|
||||
or package.get("mcp_fetch_result_parser_review")
|
||||
)
|
||||
candidate_handoff = (
|
||||
package.get("candidate_handoff")
|
||||
or package.get("handoff")
|
||||
or package.get("handoff_payload")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_handoff_review_preview(
|
||||
parser_review_package=parser_review_package,
|
||||
parser_review_result=parser_review_result,
|
||||
candidate_handoff=candidate_handoff,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_review",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_review():
|
||||
handoff_review_package = {}
|
||||
handoff_review_result = None
|
||||
candidate_queue_review = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("candidate_queue_review")
|
||||
or payload.get("queue_review")
|
||||
or payload
|
||||
)
|
||||
handoff_review_package = (
|
||||
package.get("handoff_review_package")
|
||||
or package.get("candidate_handoff_review")
|
||||
or package.get("handoff_review")
|
||||
or {}
|
||||
)
|
||||
handoff_review_result = (
|
||||
package.get("handoff_review_result")
|
||||
or package.get("mcp_fetch_candidate_handoff_review")
|
||||
)
|
||||
candidate_queue_review = (
|
||||
package.get("candidate_queue_review")
|
||||
or package.get("queue_review")
|
||||
or package.get("review_payload")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_review_preview(
|
||||
handoff_review_package=handoff_review_package,
|
||||
handoff_review_result=handoff_review_result,
|
||||
candidate_queue_review=candidate_queue_review,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_preflight",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_preflight():
|
||||
queue_review_package = {}
|
||||
queue_review_result = None
|
||||
writer_preflight = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_preflight_review")
|
||||
or payload.get("candidate_queue_writer_preflight")
|
||||
or payload.get("writer_preflight")
|
||||
or payload
|
||||
)
|
||||
queue_review_package = (
|
||||
package.get("queue_review_package")
|
||||
or package.get("candidate_queue_review")
|
||||
or package.get("queue_review")
|
||||
or {}
|
||||
)
|
||||
queue_review_result = (
|
||||
package.get("queue_review_result")
|
||||
or package.get("mcp_fetch_candidate_queue_review")
|
||||
)
|
||||
writer_preflight = (
|
||||
package.get("writer_preflight")
|
||||
or package.get("candidate_queue_writer_preflight")
|
||||
or package.get("preflight")
|
||||
or package.get("preflight_payload")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_preflight_preview(
|
||||
queue_review_package=queue_review_package,
|
||||
queue_review_result=queue_review_result,
|
||||
writer_preflight=writer_preflight,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_cli_review",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_cli_review():
|
||||
writer_preflight_package = {}
|
||||
writer_preflight_result = None
|
||||
writer_cli_review = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_cli_review_package")
|
||||
or payload.get("candidate_queue_writer_cli_review")
|
||||
or payload.get("writer_cli_review")
|
||||
or payload
|
||||
)
|
||||
writer_preflight_package = (
|
||||
package.get("writer_preflight_package")
|
||||
or package.get("candidate_queue_writer_preflight")
|
||||
or package.get("writer_preflight")
|
||||
or package.get("preflight_package")
|
||||
or {}
|
||||
)
|
||||
writer_preflight_result = (
|
||||
package.get("writer_preflight_result")
|
||||
or package.get("mcp_fetch_candidate_queue_writer_preflight")
|
||||
)
|
||||
writer_cli_review = (
|
||||
package.get("writer_cli_review")
|
||||
or package.get("candidate_queue_writer_cli_review")
|
||||
or package.get("cli_review")
|
||||
or package.get("review_payload")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_cli_review_preview(
|
||||
writer_preflight_package=writer_preflight_package,
|
||||
writer_preflight_result=writer_preflight_result,
|
||||
writer_cli_review=writer_cli_review,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_run_package_review",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_run_package_review():
|
||||
writer_cli_review_package = {}
|
||||
writer_cli_review_result = None
|
||||
writer_run_package_review = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_run_package_review_package")
|
||||
or payload.get("candidate_queue_writer_run_package_review")
|
||||
or payload.get("writer_run_package_review")
|
||||
or payload.get("run_package_review")
|
||||
or payload
|
||||
)
|
||||
writer_cli_review_package = (
|
||||
package.get("writer_cli_review_package")
|
||||
or package.get("candidate_queue_writer_cli_review")
|
||||
or package.get("writer_cli_review")
|
||||
or package.get("cli_review_package")
|
||||
or {}
|
||||
)
|
||||
writer_cli_review_result = (
|
||||
package.get("writer_cli_review_result")
|
||||
or package.get("mcp_fetch_candidate_queue_writer_cli_review")
|
||||
)
|
||||
writer_run_package_review = (
|
||||
package.get("writer_run_package_review")
|
||||
or package.get("candidate_queue_writer_run_package_review")
|
||||
or package.get("run_package_review")
|
||||
or package.get("package_review")
|
||||
or package.get("review_payload")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_run_package_review_preview(
|
||||
writer_cli_review_package=writer_cli_review_package,
|
||||
writer_cli_review_result=writer_cli_review_result,
|
||||
writer_run_package_review=writer_run_package_review,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_run_readiness",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_run_readiness():
|
||||
writer_run_package_review_package = {}
|
||||
writer_run_package_review_result = None
|
||||
operator_readiness = {}
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_run_readiness_package")
|
||||
or payload.get("candidate_queue_writer_run_readiness")
|
||||
or payload.get("writer_run_readiness")
|
||||
or payload.get("run_readiness")
|
||||
or payload
|
||||
)
|
||||
writer_run_package_review_package = (
|
||||
package.get("writer_run_package_review_package")
|
||||
or package.get("candidate_queue_writer_run_package_review")
|
||||
or package.get("writer_run_package_review")
|
||||
or package.get("run_package_review_package")
|
||||
or {}
|
||||
)
|
||||
writer_run_package_review_result = (
|
||||
package.get("writer_run_package_review_result")
|
||||
or package.get("mcp_fetch_candidate_queue_writer_run_package_review")
|
||||
)
|
||||
operator_readiness = (
|
||||
package.get("operator_readiness")
|
||||
or package.get("writer_run_readiness")
|
||||
or package.get("candidate_queue_writer_run_readiness")
|
||||
or package.get("readiness_payload")
|
||||
or {}
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_run_readiness_preview(
|
||||
writer_run_package_review_package=writer_run_package_review_package,
|
||||
writer_run_package_review_result=writer_run_package_review_result,
|
||||
operator_readiness=operator_readiness,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_run_receipt_review",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_run_receipt_review():
|
||||
writer_run_readiness_package = {}
|
||||
writer_run_readiness_result = None
|
||||
writer_run_receipt = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_run_receipt_review_package")
|
||||
or payload.get("candidate_queue_writer_run_receipt_review")
|
||||
or payload.get("writer_run_receipt")
|
||||
or payload.get("run_receipt_review")
|
||||
or payload
|
||||
)
|
||||
writer_run_readiness_package = (
|
||||
package.get("writer_run_readiness_package")
|
||||
or package.get("candidate_queue_writer_run_readiness")
|
||||
or package.get("writer_run_readiness")
|
||||
or package.get("run_readiness_package")
|
||||
or {}
|
||||
)
|
||||
writer_run_readiness_result = (
|
||||
package.get("writer_run_readiness_result")
|
||||
or package.get("mcp_fetch_candidate_queue_writer_run_readiness")
|
||||
)
|
||||
writer_run_receipt = (
|
||||
package.get("writer_run_receipt")
|
||||
or package.get("candidate_queue_writer_run_receipt")
|
||||
or package.get("run_receipt")
|
||||
or package.get("receipt_payload")
|
||||
or package.get("receipt")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_run_receipt_review_preview(
|
||||
writer_run_readiness_package=writer_run_readiness_package,
|
||||
writer_run_readiness_result=writer_run_readiness_result,
|
||||
writer_run_receipt=writer_run_receipt,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_run_closeout_review",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_run_closeout_review():
|
||||
writer_run_receipt_review_package = {}
|
||||
writer_run_receipt_review_result = None
|
||||
operator_closeout = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_run_closeout_review_package")
|
||||
or payload.get("candidate_queue_writer_run_closeout_review")
|
||||
or payload.get("writer_run_closeout")
|
||||
or payload.get("run_closeout_review")
|
||||
or payload
|
||||
)
|
||||
writer_run_receipt_review_package = (
|
||||
package.get("writer_run_receipt_review_package")
|
||||
or package.get("candidate_queue_writer_run_receipt_review")
|
||||
or package.get("writer_run_receipt_review")
|
||||
or package.get("run_receipt_review_package")
|
||||
or {}
|
||||
)
|
||||
writer_run_receipt_review_result = (
|
||||
package.get("writer_run_receipt_review_result")
|
||||
or package.get("mcp_fetch_candidate_queue_writer_run_receipt_review")
|
||||
)
|
||||
operator_closeout = (
|
||||
package.get("operator_closeout")
|
||||
or package.get("writer_run_closeout")
|
||||
or package.get("candidate_queue_writer_run_closeout")
|
||||
or package.get("closeout_payload")
|
||||
or package.get("closeout")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_run_closeout_review_preview(
|
||||
writer_run_receipt_review_package=writer_run_receipt_review_package,
|
||||
writer_run_receipt_review_result=writer_run_receipt_review_result,
|
||||
operator_closeout=operator_closeout,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_post_closeout_inventory_review",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_post_closeout_inventory_review():
|
||||
writer_run_closeout_review_package = {}
|
||||
writer_run_closeout_review_result = None
|
||||
operator_inventory = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_post_closeout_inventory_review_package")
|
||||
or payload.get("candidate_queue_writer_post_closeout_inventory_review")
|
||||
or payload.get("post_closeout_inventory_review")
|
||||
or payload.get("operator_inventory_review")
|
||||
or payload
|
||||
)
|
||||
writer_run_closeout_review_package = (
|
||||
package.get("writer_run_closeout_review_package")
|
||||
or package.get("candidate_queue_writer_run_closeout_review")
|
||||
or package.get("writer_run_closeout_review")
|
||||
or package.get("run_closeout_review_package")
|
||||
or {}
|
||||
)
|
||||
writer_run_closeout_review_result = (
|
||||
package.get("writer_run_closeout_review_result")
|
||||
or package.get("mcp_fetch_candidate_queue_writer_run_closeout_review")
|
||||
)
|
||||
operator_inventory = (
|
||||
package.get("operator_inventory")
|
||||
or package.get("post_closeout_inventory")
|
||||
or package.get("writer_post_closeout_inventory")
|
||||
or package.get("inventory_payload")
|
||||
or package.get("inventory")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_post_closeout_inventory_review_preview(
|
||||
writer_run_closeout_review_package=writer_run_closeout_review_package,
|
||||
writer_run_closeout_review_result=writer_run_closeout_review_result,
|
||||
operator_inventory=operator_inventory,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_review_handoff",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_review_handoff():
|
||||
writer_post_closeout_inventory_review_package = {}
|
||||
writer_post_closeout_inventory_review_result = None
|
||||
operator_handoff = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_review_handoff_package")
|
||||
or payload.get("candidate_queue_writer_review_handoff")
|
||||
or payload.get("review_handoff")
|
||||
or payload.get("operator_handoff_review")
|
||||
or payload.get("operator_handoff")
|
||||
or payload
|
||||
)
|
||||
writer_post_closeout_inventory_review_package = (
|
||||
package.get("writer_post_closeout_inventory_review_package")
|
||||
or package.get("candidate_queue_writer_post_closeout_inventory_review")
|
||||
or package.get("writer_post_closeout_inventory_review")
|
||||
or package.get("post_closeout_inventory_review_package")
|
||||
or package.get("post_closeout_inventory_review")
|
||||
or package.get("operator_inventory_review")
|
||||
or {}
|
||||
)
|
||||
writer_post_closeout_inventory_review_result = (
|
||||
package.get("writer_post_closeout_inventory_review_result")
|
||||
or package.get(
|
||||
"mcp_fetch_candidate_queue_writer_post_closeout_inventory_review"
|
||||
)
|
||||
)
|
||||
operator_handoff = (
|
||||
package.get("operator_handoff")
|
||||
or package.get("candidate_queue_review_handoff")
|
||||
or package.get("writer_review_handoff")
|
||||
or package.get("handoff_payload")
|
||||
or package.get("handoff")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_review_handoff_preview(
|
||||
writer_post_closeout_inventory_review_package=(
|
||||
writer_post_closeout_inventory_review_package
|
||||
),
|
||||
writer_post_closeout_inventory_review_result=(
|
||||
writer_post_closeout_inventory_review_result
|
||||
),
|
||||
operator_handoff=operator_handoff,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_review_inventory",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_review_inventory():
|
||||
writer_review_handoff_package = {}
|
||||
writer_review_handoff_result = None
|
||||
operator_review_inventory = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_review_inventory_package")
|
||||
or payload.get("candidate_queue_writer_review_inventory")
|
||||
or payload.get("review_inventory")
|
||||
or payload.get("operator_review_inventory")
|
||||
or payload
|
||||
)
|
||||
writer_review_handoff_package = (
|
||||
package.get("writer_review_handoff_package")
|
||||
or package.get("candidate_queue_writer_review_handoff")
|
||||
or package.get("writer_review_handoff")
|
||||
or package.get("review_handoff_package")
|
||||
or package.get("review_handoff")
|
||||
or {}
|
||||
)
|
||||
writer_review_handoff_result = (
|
||||
package.get("writer_review_handoff_result")
|
||||
or package.get("mcp_fetch_candidate_queue_writer_review_handoff")
|
||||
)
|
||||
operator_review_inventory = (
|
||||
package.get("operator_review_inventory")
|
||||
or package.get("candidate_queue_review_inventory")
|
||||
or package.get("writer_review_inventory")
|
||||
or package.get("review_inventory_payload")
|
||||
or package.get("inventory_payload")
|
||||
or package.get("inventory")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_review_inventory_preview(
|
||||
writer_review_handoff_package=writer_review_handoff_package,
|
||||
writer_review_handoff_result=writer_review_handoff_result,
|
||||
operator_review_inventory=operator_review_inventory,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@market_intel_bp.route(
|
||||
"/api/market_intel/mcp_fetch_candidate_queue_writer_review_decision",
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
@login_required
|
||||
def market_intel_mcp_fetch_candidate_queue_writer_review_decision():
|
||||
writer_review_inventory_package = {}
|
||||
writer_review_inventory_result = None
|
||||
operator_review_decision = None
|
||||
if request.method == "POST":
|
||||
payload = request.get_json(silent=True) or {}
|
||||
package = (
|
||||
payload.get("writer_review_decision_package")
|
||||
or payload.get("candidate_queue_writer_review_decision")
|
||||
or payload.get("review_decision")
|
||||
or payload.get("operator_review_decision")
|
||||
or payload
|
||||
)
|
||||
writer_review_inventory_package = (
|
||||
package.get("writer_review_inventory_package")
|
||||
or package.get("candidate_queue_writer_review_inventory")
|
||||
or package.get("writer_review_inventory")
|
||||
or package.get("review_inventory_package")
|
||||
or package.get("review_inventory")
|
||||
or {}
|
||||
)
|
||||
writer_review_inventory_result = (
|
||||
package.get("writer_review_inventory_result")
|
||||
or package.get("mcp_fetch_candidate_queue_writer_review_inventory")
|
||||
)
|
||||
operator_review_decision = (
|
||||
package.get("operator_review_decision")
|
||||
or package.get("candidate_queue_review_decision")
|
||||
or package.get("writer_review_decision")
|
||||
or package.get("review_decision_payload")
|
||||
or package.get("decision_payload")
|
||||
or package.get("decision")
|
||||
)
|
||||
|
||||
service = MarketIntelService()
|
||||
return jsonify(
|
||||
build_mcp_fetch_candidate_queue_writer_review_decision_preview(
|
||||
writer_review_inventory_package=writer_review_inventory_package,
|
||||
writer_review_inventory_result=writer_review_inventory_result,
|
||||
operator_review_decision=operator_review_decision,
|
||||
phase=service.phase,
|
||||
)
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user