tt-help-cli-ycl 1.4.17 → 1.4.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,7 +22,13 @@ export function insertTag(tag, countries, source = "llm") {
22
22
  return { inserted: false, error: "invalid tag" };
23
23
  }
24
24
  try {
25
- // ON CONFLICT: 合并 countries 数组,避免跨国家共享 tag 时信息丢失
25
+ // 先查是否已存在且为 dead 状态,如果是则重置为 new(给二次机会)
26
+ const existing = db
27
+ .prepare("SELECT status FROM tags WHERE tag = ?")
28
+ .get(normalized);
29
+ const resetDead = existing && existing.status === "dead";
30
+
31
+ // ON CONFLICT: 合并 countries 数组;如果原状态是 dead 则重置为 new
26
32
  const result = db
27
33
  .prepare(
28
34
  `INSERT INTO tags (tag, countries, source) VALUES (@tag, @countries, @source)
@@ -32,9 +38,12 @@ export function insertTag(tag, countries, source = "llm") {
32
38
  UNION
33
39
  SELECT value FROM json_each(@countries)
34
40
  )
35
- )`,
41
+ )${resetDead ? ", status = 'new', score = NULL, last_error = NULL, dead_reason = NULL, scored_at = NULL, score_count = 0" : ""}`,
36
42
  )
37
43
  .run({ tag: normalized, countries: JSON.stringify(countries), source });
44
+ if (resetDead) {
45
+ console.error(`[tags] 🔄 dead tag #${normalized} 重新生成,重置为 new`);
46
+ }
38
47
  return { inserted: result.changes > 0, tag: normalized };
39
48
  } catch (e) {
40
49
  return { inserted: false, error: e.message };
@@ -55,7 +64,8 @@ export function getTagsByStatus(
55
64
  sql += " AND countries LIKE ?";
56
65
  params.push(`%"${country}"%`);
57
66
  }
58
- sql += " ORDER BY score ASC, created_at ASC LIMIT ? OFFSET ?";
67
+ sql +=
68
+ " ORDER BY refreshed_at DESC NULLS LAST, score ASC, created_at ASC LIMIT ? OFFSET ?";
59
69
  params.push(limit, offset);
60
70
  const rows = db.prepare(sql).all(...params);
61
71
  return rows.map(parseTagRow);
@@ -96,6 +106,48 @@ export function getTagStats(country = null) {
96
106
  };
97
107
  }
98
108
 
109
+ export function getRefreshStats(hours = 24) {
110
+ const db = getDb();
111
+ if (!db) return { countries: [], total: 0, refreshed: 0 };
112
+
113
+ const rows = db
114
+ .prepare(
115
+ "SELECT countries FROM tags WHERE status = 'productive' AND score > 70",
116
+ )
117
+ .all();
118
+ const countrySet = new Set();
119
+ for (const r of rows) {
120
+ try {
121
+ for (const c of JSON.parse(r.countries || "[]")) countrySet.add(c);
122
+ } catch {}
123
+ }
124
+
125
+ const results = [];
126
+ let totalAll = 0;
127
+ let refreshedAll = 0;
128
+ const countryList = [...countrySet].sort();
129
+
130
+ const stmtTotal = db.prepare(
131
+ "SELECT COUNT(*) as cnt FROM tags WHERE status = 'productive' AND score > 70 AND countries LIKE ?",
132
+ );
133
+ const stmtRefresh = db.prepare(
134
+ `SELECT COUNT(*) as cnt FROM tags WHERE status = 'productive' AND score > 70 AND countries LIKE ? AND refreshed_at >= datetime('now', ?)`,
135
+ );
136
+
137
+ for (const c of countryList) {
138
+ const like = `%"${c}"%`;
139
+ const total = stmtTotal.get(like).cnt;
140
+ const refreshed = stmtRefresh.get(like, `-${hours} hours`).cnt;
141
+ if (total > 0) {
142
+ totalAll += total;
143
+ refreshedAll += refreshed;
144
+ results.push({ country: c, total, refreshed });
145
+ }
146
+ }
147
+
148
+ return { countries: results, total: totalAll, refreshed: refreshedAll };
149
+ }
150
+
99
151
  export function getTagsByCountry(country, minScore = 0) {
100
152
  const db = getDb();
101
153
  if (!db) return [];
@@ -233,7 +285,8 @@ export function getAllTags(limit = 200, offset = 0, country = null) {
233
285
  sql += " WHERE countries LIKE ?";
234
286
  params.push(`%"${country}"%`);
235
287
  }
236
- sql += " ORDER BY score DESC, created_at DESC LIMIT ? OFFSET ?";
288
+ sql +=
289
+ " ORDER BY refreshed_at DESC NULLS LAST, score DESC, created_at DESC LIMIT ? OFFSET ?";
237
290
  params.push(limit, offset);
238
291
  const rows = db.prepare(sql).all(...params);
239
292
  return rows.map(parseTagRow);
@@ -319,6 +372,7 @@ export function insertDiscoverLog({
319
372
  round,
320
373
  strategy,
321
374
  tagsGenerated,
375
+ tagsRaw,
322
376
  tagsAdded,
323
377
  productiveSample,
324
378
  deadSample,
@@ -332,15 +386,16 @@ export function insertDiscoverLog({
332
386
  const result = db
333
387
  .prepare(
334
388
  `INSERT INTO discover_log
335
- (country, round, strategy, tags_generated, tags_added,
389
+ (country, round, strategy, tags_generated, tags_raw, tags_added,
336
390
  productive_sample, dead_sample, avg_productive_score, avg_dead_score)
337
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
391
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
338
392
  )
339
393
  .run(
340
394
  country,
341
395
  round,
342
396
  strategy || null,
343
397
  JSON.stringify(tagsGenerated || []),
398
+ tagsRaw ? JSON.stringify(tagsRaw) : null,
344
399
  tagsAdded || 0,
345
400
  JSON.stringify(productiveSample || []),
346
401
  JSON.stringify(deadSample || []),
@@ -386,6 +441,51 @@ export function getAllExistingTagNames() {
386
441
  return new Set(rows.map((r) => r.tag));
387
442
  }
388
443
 
444
+ /**
445
+ * 获取活跃 tag 名集合(仅 productive + scored),用于全局去重
446
+ * 排除 dead tag,因为 insertTag 会在冲突时将 dead 重置为 new。
447
+ */
448
+ export function getActiveTagNames() {
449
+ const db = getDb();
450
+ if (!db) return new Set();
451
+ const rows = db
452
+ .prepare("SELECT tag FROM tags WHERE status IN ('productive', 'scored')")
453
+ .all();
454
+ return new Set(rows.map((r) => r.tag));
455
+ }
456
+
457
+ /**
458
+ * 获取某国家最近一轮 discover 中 LLM 原始生成的全部 tag(去重前)
459
+ * 用于反馈给 LLM:"你上轮生成了这些,全部已被数据库覆盖,换方向"
460
+ */
461
+ export function getLastRoundAllGeneratedTags(country) {
462
+ const db = getDb();
463
+ if (!db) return null;
464
+
465
+ const row = db
466
+ .prepare(
467
+ `SELECT tags_raw, tags_generated, tags_added FROM discover_log
468
+ WHERE country = ?
469
+ ORDER BY round DESC
470
+ LIMIT 1`,
471
+ )
472
+ .get(country);
473
+
474
+ if (!row) return null;
475
+
476
+ const raw = JSON.parse(row.tags_raw || "[]");
477
+ const survived = JSON.parse(row.tags_generated || "[]");
478
+ const rejected = raw.filter((t) => !survived.includes(t));
479
+
480
+ return {
481
+ raw,
482
+ survived,
483
+ rejected,
484
+ tagsAdded: row.tags_added,
485
+ allRejected: row.tags_added === 0 && raw.length > 0,
486
+ };
487
+ }
488
+
389
489
  export function getRecentGeneratedTags(country, lookback = 10) {
390
490
  const db = getDb();
391
491
  if (!db) return [];