@tikomni/skills 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/creator-analysis/SKILL.md +34 -10
- package/skills/creator-analysis/references/contracts/creator-card-fields.md +2 -0
- package/skills/creator-analysis/references/contracts/work-card-fields.md +40 -4
- package/skills/creator-analysis/references/platform-guides/douyin.md +41 -36
- package/skills/creator-analysis/references/platform-guides/generic.md +11 -7
- package/skills/creator-analysis/references/platform-guides/xiaohongshu.md +45 -30
- package/skills/creator-analysis/references/schemas/author-analysis-v2.schema.json +224 -95
- package/skills/creator-analysis/references/workflow.md +8 -3
- package/skills/creator-analysis/scripts/author_home/adapters/platform_adapters.py +205 -21
- package/skills/creator-analysis/scripts/author_home/analyzers/author_analysis_v2_support.py +54 -11
- package/skills/creator-analysis/scripts/author_home/analyzers/prompt_first_analyzers.py +200 -13
- package/skills/creator-analysis/scripts/author_home/analyzers/sampled_work_batch_explainer.py +113 -42
- package/skills/creator-analysis/scripts/author_home/asr/home_asr.py +65 -7
- package/skills/creator-analysis/scripts/author_home/builders/home_builders.py +82 -18
- package/skills/creator-analysis/scripts/author_home/collectors/homepage_collectors.py +198 -32
- package/skills/creator-analysis/scripts/author_home/orchestrator/run_author_analysis.py +374 -31
- package/skills/creator-analysis/scripts/author_home/orchestrator/work_analysis_artifacts.py +68 -12
- package/skills/creator-analysis/scripts/core/storage_router.py +3 -0
- package/skills/creator-analysis/scripts/writers/write_author_homepage_samples.py +3 -2
- package/skills/creator-analysis/scripts/writers/write_benchmark_card.py +314 -137
|
@@ -22,6 +22,7 @@ from scripts.core.config_loader import resolve_storage_paths
|
|
|
22
22
|
from scripts.core.storage_router import render_output_filename, resolve_json_filename_pattern
|
|
23
23
|
|
|
24
24
|
from scripts.author_home.adapters.platform_adapters import adapt_douyin_author_home, adapt_xhs_author_home
|
|
25
|
+
from scripts.author_home.analyzers.author_analysis_v2_support import prepare_author_analysis_bundle
|
|
25
26
|
from scripts.author_home.orchestrator.work_analysis_artifacts import orchestrate_work_analysis_artifacts
|
|
26
27
|
from scripts.core.progress_report import ProgressReporter
|
|
27
28
|
from scripts.author_home.analyzers.prompt_first_analyzers import run_prompt_first_author_analysis
|
|
@@ -32,6 +33,145 @@ from scripts.author_home.collectors.homepage_collectors import collect_douyin_au
|
|
|
32
33
|
CollectorFn = Callable[..., Dict[str, Any]]
|
|
33
34
|
|
|
34
35
|
|
|
36
|
+
def _stage_status(*, status: str, ok_count: int, failed_count: int, degraded_count: int, reason_codes: List[str], failure_kind: str = "") -> Dict[str, Any]:
|
|
37
|
+
return {
|
|
38
|
+
"status": status,
|
|
39
|
+
"ok_count": ok_count,
|
|
40
|
+
"failed_count": failed_count,
|
|
41
|
+
"degraded_count": degraded_count,
|
|
42
|
+
"reason_codes": list(dict.fromkeys([code for code in reason_codes if code])),
|
|
43
|
+
"failure_kind": failure_kind or None,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _reason_codes_from_failed_items(items: List[Dict[str, Any]]) -> List[str]:
|
|
48
|
+
reason_codes: List[str] = []
|
|
49
|
+
for item in items:
|
|
50
|
+
if not isinstance(item, dict):
|
|
51
|
+
continue
|
|
52
|
+
reason = str(item.get("error_reason") or "").strip()
|
|
53
|
+
if not reason:
|
|
54
|
+
continue
|
|
55
|
+
reason_codes.append(reason.split(":", 1)[0])
|
|
56
|
+
return list(dict.fromkeys(reason_codes))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _merge_normalized_works(*, works: List[Dict[str, Any]], normalized_works: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
60
|
+
normalized_map = {
|
|
61
|
+
str(item.get("platform_work_id") or "").strip(): item
|
|
62
|
+
for item in normalized_works
|
|
63
|
+
if isinstance(item, dict) and str(item.get("platform_work_id") or "").strip()
|
|
64
|
+
}
|
|
65
|
+
merged: List[Dict[str, Any]] = []
|
|
66
|
+
for work in works:
|
|
67
|
+
if not isinstance(work, dict):
|
|
68
|
+
continue
|
|
69
|
+
platform_work_id = str(work.get("platform_work_id") or "").strip()
|
|
70
|
+
normalized = normalized_map.get(platform_work_id)
|
|
71
|
+
if not isinstance(normalized, dict):
|
|
72
|
+
merged.append(work)
|
|
73
|
+
continue
|
|
74
|
+
merged_work = dict(work)
|
|
75
|
+
for field in (
|
|
76
|
+
"primary_text",
|
|
77
|
+
"primary_text_source",
|
|
78
|
+
"digg_count",
|
|
79
|
+
"comment_count",
|
|
80
|
+
"collect_count",
|
|
81
|
+
"share_count",
|
|
82
|
+
"play_count",
|
|
83
|
+
"performance_score",
|
|
84
|
+
"performance_score_norm",
|
|
85
|
+
"bucket",
|
|
86
|
+
"hook_type",
|
|
87
|
+
"structure_type",
|
|
88
|
+
"cta_type",
|
|
89
|
+
"content_form",
|
|
90
|
+
"style_markers",
|
|
91
|
+
"analysis_eligibility",
|
|
92
|
+
"analysis_exclusion_reason",
|
|
93
|
+
"published_date",
|
|
94
|
+
"publish_time",
|
|
95
|
+
):
|
|
96
|
+
if field in normalized:
|
|
97
|
+
merged_work[field] = normalized.get(field)
|
|
98
|
+
merged.append(merged_work)
|
|
99
|
+
return merged
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _build_card_stage_status(*, expected_count: int, results: Dict[str, Any], failure_kind: str = "runtime") -> Dict[str, Any]:
|
|
103
|
+
if not isinstance(results, dict) or not results.get("enabled", True):
|
|
104
|
+
return _stage_status(status="skipped", ok_count=0, failed_count=0, degraded_count=0, reason_codes=["write_card_disabled"])
|
|
105
|
+
ok_count = int(results.get("count") or len(results.get("results") or []))
|
|
106
|
+
failed_items = results.get("failed_items") if isinstance(results.get("failed_items"), list) else []
|
|
107
|
+
degraded_count = len(failed_items) if ok_count > 0 else 0
|
|
108
|
+
failed_count = len(failed_items) if ok_count == 0 and expected_count > 0 else 0
|
|
109
|
+
if failed_count > 0:
|
|
110
|
+
return _stage_status(
|
|
111
|
+
status="failed",
|
|
112
|
+
ok_count=ok_count,
|
|
113
|
+
failed_count=failed_count,
|
|
114
|
+
degraded_count=0,
|
|
115
|
+
reason_codes=_reason_codes_from_failed_items(failed_items) or ["card_write_failed"],
|
|
116
|
+
failure_kind=failure_kind,
|
|
117
|
+
)
|
|
118
|
+
if degraded_count > 0:
|
|
119
|
+
return _stage_status(
|
|
120
|
+
status="degraded",
|
|
121
|
+
ok_count=ok_count,
|
|
122
|
+
failed_count=0,
|
|
123
|
+
degraded_count=degraded_count,
|
|
124
|
+
reason_codes=_reason_codes_from_failed_items(failed_items) or ["card_write_degraded"],
|
|
125
|
+
failure_kind=failure_kind,
|
|
126
|
+
)
|
|
127
|
+
return _stage_status(status="full", ok_count=ok_count, failed_count=0, degraded_count=0, reason_codes=[])
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _build_persist_stage_status(persist_result: Dict[str, Any]) -> Dict[str, Any]:
|
|
131
|
+
if not isinstance(persist_result, dict) or not persist_result.get("enabled", True):
|
|
132
|
+
return _stage_status(status="skipped", ok_count=0, failed_count=0, degraded_count=0, reason_codes=["persist_disabled"])
|
|
133
|
+
if persist_result.get("ok"):
|
|
134
|
+
return _stage_status(status="full", ok_count=1, failed_count=0, degraded_count=0, reason_codes=[])
|
|
135
|
+
error_reason = str(persist_result.get("error") or persist_result.get("failure_reason") or "persist_failed")
|
|
136
|
+
return _stage_status(
|
|
137
|
+
status="failed",
|
|
138
|
+
ok_count=0,
|
|
139
|
+
failed_count=1,
|
|
140
|
+
degraded_count=0,
|
|
141
|
+
reason_codes=[error_reason.split(":", 1)[0]],
|
|
142
|
+
failure_kind="configuration" if error_reason.startswith("resolve_storage_paths_failed:") else "runtime",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _build_overall_status(stage_status: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
|
|
147
|
+
ordered = [
|
|
148
|
+
"fetch",
|
|
149
|
+
"asr",
|
|
150
|
+
"sampled_explanations",
|
|
151
|
+
"author_analysis",
|
|
152
|
+
"author_sample_card",
|
|
153
|
+
"sample_work_card",
|
|
154
|
+
"author_card",
|
|
155
|
+
]
|
|
156
|
+
stages = [stage_status.get(key) if isinstance(stage_status.get(key), dict) else {} for key in ordered]
|
|
157
|
+
if any(stage.get("failure_kind") == "configuration" or stage.get("status") == "failed" and stage.get("failure_kind") == "configuration" for stage in stages):
|
|
158
|
+
reason_codes: List[str] = []
|
|
159
|
+
for stage in stages:
|
|
160
|
+
if stage.get("failure_kind") == "configuration":
|
|
161
|
+
reason_codes.extend(stage.get("reason_codes") or [])
|
|
162
|
+
return _stage_status(status="failed", ok_count=0, failed_count=1, degraded_count=0, reason_codes=reason_codes or ["configuration_failed"], failure_kind="configuration")
|
|
163
|
+
author_analysis = stage_status.get("author_analysis") if isinstance(stage_status.get("author_analysis"), dict) else {}
|
|
164
|
+
author_card = stage_status.get("author_card") if isinstance(stage_status.get("author_card"), dict) else {}
|
|
165
|
+
if author_analysis.get("status") == "fallback" and author_card.get("status") in {"full", "degraded", "fallback"}:
|
|
166
|
+
return _stage_status(status="fallback", ok_count=0, failed_count=0, degraded_count=0, reason_codes=author_analysis.get("reason_codes") or ["author_analysis_fallback"], failure_kind=author_analysis.get("failure_kind") or "runtime")
|
|
167
|
+
if any((stage.get("failed_count") or 0) > 0 or (stage.get("degraded_count") or 0) > 0 or stage.get("status") in {"degraded", "failed"} for stage in stages):
|
|
168
|
+
reason_codes = []
|
|
169
|
+
for stage in stages:
|
|
170
|
+
reason_codes.extend(stage.get("reason_codes") or [])
|
|
171
|
+
return _stage_status(status="degraded", ok_count=0, failed_count=0, degraded_count=1, reason_codes=reason_codes or ["stage_degraded"])
|
|
172
|
+
return _stage_status(status="full", ok_count=1, failed_count=0, degraded_count=0, reason_codes=[])
|
|
173
|
+
|
|
174
|
+
|
|
35
175
|
def _unsupported(platform: str) -> Dict[str, Any]:
|
|
36
176
|
return {
|
|
37
177
|
"platform": platform,
|
|
@@ -44,6 +184,18 @@ def _unsupported(platform: str) -> Dict[str, Any]:
|
|
|
44
184
|
"missing_fields": [],
|
|
45
185
|
"extract_trace": [],
|
|
46
186
|
"fallback_trace": [],
|
|
187
|
+
"stage_status": {
|
|
188
|
+
"fetch": _stage_status(status="failed", ok_count=0, failed_count=1, degraded_count=0, reason_codes=["unsupported_platform"]),
|
|
189
|
+
"asr": _stage_status(status="skipped", ok_count=0, failed_count=0, degraded_count=0, reason_codes=["unsupported_platform"]),
|
|
190
|
+
"author_sample_card": _stage_status(status="skipped", ok_count=0, failed_count=0, degraded_count=0, reason_codes=["unsupported_platform"]),
|
|
191
|
+
"sample_work_card": _stage_status(status="skipped", ok_count=0, failed_count=0, degraded_count=0, reason_codes=["unsupported_platform"]),
|
|
192
|
+
"sampled_explanations": _stage_status(status="skipped", ok_count=0, failed_count=0, degraded_count=0, reason_codes=["unsupported_platform"]),
|
|
193
|
+
"author_analysis": _stage_status(status="skipped", ok_count=0, failed_count=0, degraded_count=0, reason_codes=["unsupported_platform"]),
|
|
194
|
+
"author_card": _stage_status(status="skipped", ok_count=0, failed_count=0, degraded_count=0, reason_codes=["unsupported_platform"]),
|
|
195
|
+
"persist": _stage_status(status="skipped", ok_count=0, failed_count=0, degraded_count=0, reason_codes=["unsupported_platform"]),
|
|
196
|
+
"overall": _stage_status(status="failed", ok_count=0, failed_count=1, degraded_count=0, reason_codes=["unsupported_platform"]),
|
|
197
|
+
},
|
|
198
|
+
"quality_tier": "failed",
|
|
47
199
|
"request_id": None,
|
|
48
200
|
}
|
|
49
201
|
|
|
@@ -112,14 +264,29 @@ def _build_persist_payload(*, result: Dict[str, Any], status: str, written_at: d
|
|
|
112
264
|
}
|
|
113
265
|
|
|
114
266
|
|
|
115
|
-
def _persist_output_artifact(*, result: Dict[str, Any], input_value: str, storage_config: Dict[str, Any], persist_output: bool) -> Dict[str, Any]:
|
|
267
|
+
def _persist_output_artifact(*, result: Dict[str, Any], input_value: str, storage_config: Dict[str, Any], persist_output: bool, card_root: Optional[str]) -> Dict[str, Any]:
|
|
116
268
|
if not persist_output:
|
|
117
269
|
return {"enabled": False, "skipped": True, "reason": "disabled_by_flag"}
|
|
118
270
|
|
|
271
|
+
diagnostics = {
|
|
272
|
+
"effective_card_root": str(card_root or ""),
|
|
273
|
+
"results_root": "",
|
|
274
|
+
"errors_root": "",
|
|
275
|
+
}
|
|
119
276
|
try:
|
|
120
277
|
paths = resolve_storage_paths(storage_config or {})
|
|
121
278
|
except Exception as error:
|
|
122
|
-
return {
|
|
279
|
+
return {
|
|
280
|
+
"enabled": True,
|
|
281
|
+
"ok": False,
|
|
282
|
+
"error": f"resolve_storage_paths_failed:{error}",
|
|
283
|
+
"failure_reason": f"resolve_storage_paths_failed:{error}",
|
|
284
|
+
"paths": diagnostics,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
diagnostics["effective_card_root"] = str(card_root or paths.get("root_dir") or "")
|
|
288
|
+
diagnostics["results_root"] = str(paths.get("results_root") or "")
|
|
289
|
+
diagnostics["errors_root"] = str(paths.get("errors_root") or "")
|
|
123
290
|
|
|
124
291
|
now = datetime.now()
|
|
125
292
|
date_key = now.strftime("%Y%m%d")
|
|
@@ -134,7 +301,17 @@ def _persist_output_artifact(*, result: Dict[str, Any], input_value: str, storag
|
|
|
134
301
|
else:
|
|
135
302
|
target_dir = Path(paths.get("results_root", "")) / date_key
|
|
136
303
|
|
|
137
|
-
|
|
304
|
+
try:
|
|
305
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
306
|
+
except Exception as error:
|
|
307
|
+
return {
|
|
308
|
+
"enabled": True,
|
|
309
|
+
"ok": False,
|
|
310
|
+
"error": f"persist_target_dir_failed:{type(error).__name__}:{error}",
|
|
311
|
+
"failure_reason": f"persist_target_dir_failed:{type(error).__name__}:{error}",
|
|
312
|
+
"paths": diagnostics,
|
|
313
|
+
"target_dir": str(target_dir),
|
|
314
|
+
}
|
|
138
315
|
file_name = render_output_filename(
|
|
139
316
|
pattern=resolve_json_filename_pattern(storage_config),
|
|
140
317
|
context={
|
|
@@ -158,12 +335,23 @@ def _persist_output_artifact(*, result: Dict[str, Any], input_value: str, storag
|
|
|
158
335
|
written_at=now,
|
|
159
336
|
identifier=identifier,
|
|
160
337
|
)
|
|
161
|
-
|
|
338
|
+
try:
|
|
339
|
+
file_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
340
|
+
except Exception as error:
|
|
341
|
+
return {
|
|
342
|
+
"enabled": True,
|
|
343
|
+
"ok": False,
|
|
344
|
+
"error": f"persist_write_failed:{type(error).__name__}:{error}",
|
|
345
|
+
"failure_reason": f"persist_write_failed:{type(error).__name__}:{error}",
|
|
346
|
+
"paths": diagnostics,
|
|
347
|
+
"path": str(file_path),
|
|
348
|
+
}
|
|
162
349
|
return {
|
|
163
350
|
"enabled": True,
|
|
164
351
|
"ok": True,
|
|
165
352
|
"status": status,
|
|
166
353
|
"path": str(file_path),
|
|
354
|
+
"paths": diagnostics,
|
|
167
355
|
}
|
|
168
356
|
|
|
169
357
|
|
|
@@ -268,6 +456,21 @@ def run_author_home_analysis(
|
|
|
268
456
|
progress=progress.child(scope="author_home.asr") if progress is not None else None,
|
|
269
457
|
)
|
|
270
458
|
works = list(asr_bundle.get("works") or [])[:capped_max_items]
|
|
459
|
+
prepared_analysis_bundle = prepare_author_analysis_bundle(
|
|
460
|
+
profile=profile,
|
|
461
|
+
works=works,
|
|
462
|
+
platform=platform,
|
|
463
|
+
)
|
|
464
|
+
works = _merge_normalized_works(
|
|
465
|
+
works=works,
|
|
466
|
+
normalized_works=prepared_analysis_bundle.get("normalized_works") if isinstance(prepared_analysis_bundle.get("normalized_works"), list) else [],
|
|
467
|
+
)
|
|
468
|
+
prepared_analysis_bundle = prepare_author_analysis_bundle(
|
|
469
|
+
profile=profile,
|
|
470
|
+
works=works,
|
|
471
|
+
platform=platform,
|
|
472
|
+
)
|
|
473
|
+
sampled_work_ids = prepared_analysis_bundle.get("sampled_work_ids") if isinstance(prepared_analysis_bundle.get("sampled_work_ids"), list) else []
|
|
271
474
|
|
|
272
475
|
if write_card:
|
|
273
476
|
work_analysis_bundle = orchestrate_work_analysis_artifacts(
|
|
@@ -296,6 +499,7 @@ def run_author_home_analysis(
|
|
|
296
499
|
"artifact_root": None,
|
|
297
500
|
"analysis_logic_version": None,
|
|
298
501
|
"prompt_contract_hash": None,
|
|
502
|
+
"normalization_version": None,
|
|
299
503
|
}
|
|
300
504
|
work_analysis_stats = work_analysis_bundle.get("stats") if isinstance(work_analysis_bundle.get("stats"), dict) else {}
|
|
301
505
|
work_analysis_failed = work_analysis_bundle.get("failed_items") if isinstance(work_analysis_bundle.get("failed_items"), list) else []
|
|
@@ -317,7 +521,11 @@ def run_author_home_analysis(
|
|
|
317
521
|
|
|
318
522
|
if progress is not None:
|
|
319
523
|
progress.progress(stage="author_home.analysis", message="running author analysis")
|
|
320
|
-
analysis, analysis_missing, analysis_trace = run_prompt_first_author_analysis(
|
|
524
|
+
analysis, analysis_missing, analysis_trace = run_prompt_first_author_analysis(
|
|
525
|
+
profile,
|
|
526
|
+
works,
|
|
527
|
+
analysis_bundle=prepared_analysis_bundle,
|
|
528
|
+
)
|
|
321
529
|
if progress is not None:
|
|
322
530
|
progress.done(
|
|
323
531
|
stage="author_home.analysis",
|
|
@@ -336,19 +544,17 @@ def run_author_home_analysis(
|
|
|
336
544
|
profile=profile,
|
|
337
545
|
works=works,
|
|
338
546
|
render_payloads=render_payloads,
|
|
547
|
+
sampled_work_ids=sampled_work_ids,
|
|
548
|
+
sampled_work_explanations=(
|
|
549
|
+
analysis.get("sampled_work_explanations", {}).get("sampled_work_explanations")
|
|
550
|
+
if isinstance(analysis.get("sampled_work_explanations"), dict)
|
|
551
|
+
else {}
|
|
552
|
+
),
|
|
339
553
|
card_root=card_root,
|
|
340
554
|
storage_config=storage_config,
|
|
341
555
|
write_card=write_card,
|
|
342
556
|
failed_items=work_analysis_failed,
|
|
343
557
|
)
|
|
344
|
-
author_card_write = build_author_card(
|
|
345
|
-
platform=platform,
|
|
346
|
-
profile=profile,
|
|
347
|
-
analysis_payload=analysis,
|
|
348
|
-
card_root=card_root,
|
|
349
|
-
storage_config=storage_config,
|
|
350
|
-
write_card=write_card,
|
|
351
|
-
)
|
|
352
558
|
|
|
353
559
|
asr_trace = list(asr_bundle.get("trace") or [])
|
|
354
560
|
all_extract_trace = list(raw.get("extract_trace") or []) + asr_trace + work_analysis_trace + analysis_trace
|
|
@@ -378,19 +584,135 @@ def run_author_home_analysis(
|
|
|
378
584
|
}
|
|
379
585
|
)
|
|
380
586
|
|
|
381
|
-
work_analysis_missing: List[Dict[str, str]] = []
|
|
382
|
-
for item in work_analysis_failed:
|
|
383
|
-
if not isinstance(item, dict):
|
|
384
|
-
continue
|
|
385
|
-
work_analysis_missing.append(
|
|
386
|
-
{
|
|
387
|
-
"field": f"works[{item.get('platform_work_id') or 'unknown'}].analysis_artifact",
|
|
388
|
-
"reason": str(item.get("error_reason") or "work_analysis_artifact_failed"),
|
|
389
|
-
}
|
|
390
|
-
)
|
|
391
|
-
|
|
392
587
|
asr_stats = asr_bundle.get("stats") if isinstance(asr_bundle.get("stats"), dict) else {}
|
|
393
588
|
asr_checkpoint = asr_bundle.get("checkpoint") if isinstance(asr_bundle.get("checkpoint"), dict) else {}
|
|
589
|
+
author_sample_cards = work_card_write.get("author_sample_cards") if isinstance(work_card_write.get("author_sample_cards"), dict) else {}
|
|
590
|
+
sample_work_cards = work_card_write.get("sample_work_cards") if isinstance(work_card_write.get("sample_work_cards"), dict) else {}
|
|
591
|
+
|
|
592
|
+
eligible_count = len([item for item in works if isinstance(item, dict) and str(item.get("analysis_eligibility") or "") == "eligible"])
|
|
593
|
+
incomplete_count = len([item for item in works if isinstance(item, dict) and str(item.get("analysis_eligibility") or "") != "eligible"])
|
|
594
|
+
if works and eligible_count == 0:
|
|
595
|
+
asr_stage = _stage_status(
|
|
596
|
+
status="failed",
|
|
597
|
+
ok_count=0,
|
|
598
|
+
failed_count=incomplete_count or len(works),
|
|
599
|
+
degraded_count=0,
|
|
600
|
+
reason_codes=["asr_unavailable_for_all_works"],
|
|
601
|
+
failure_kind="runtime",
|
|
602
|
+
)
|
|
603
|
+
elif incomplete_count > 0:
|
|
604
|
+
asr_stage = _stage_status(
|
|
605
|
+
status="degraded",
|
|
606
|
+
ok_count=eligible_count,
|
|
607
|
+
failed_count=0,
|
|
608
|
+
degraded_count=incomplete_count,
|
|
609
|
+
reason_codes=["partial_asr_unavailable"],
|
|
610
|
+
failure_kind="runtime",
|
|
611
|
+
)
|
|
612
|
+
else:
|
|
613
|
+
asr_stage = _stage_status(
|
|
614
|
+
status="full",
|
|
615
|
+
ok_count=eligible_count,
|
|
616
|
+
failed_count=0,
|
|
617
|
+
degraded_count=0,
|
|
618
|
+
reason_codes=[],
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
stage_status: Dict[str, Dict[str, Any]] = {
|
|
622
|
+
"fetch": _stage_status(
|
|
623
|
+
status="full",
|
|
624
|
+
ok_count=len(works),
|
|
625
|
+
failed_count=0,
|
|
626
|
+
degraded_count=0,
|
|
627
|
+
reason_codes=[],
|
|
628
|
+
),
|
|
629
|
+
"asr": asr_stage,
|
|
630
|
+
"author_sample_card": _build_card_stage_status(
|
|
631
|
+
expected_count=len(works),
|
|
632
|
+
results=author_sample_cards,
|
|
633
|
+
),
|
|
634
|
+
"sample_work_card": _build_card_stage_status(
|
|
635
|
+
expected_count=len(sampled_work_ids),
|
|
636
|
+
results=sample_work_cards,
|
|
637
|
+
),
|
|
638
|
+
"sampled_explanations": analysis.get("sampled_explanations_status") if isinstance(analysis.get("sampled_explanations_status"), dict) else _stage_status(
|
|
639
|
+
status="failed",
|
|
640
|
+
ok_count=0,
|
|
641
|
+
failed_count=1,
|
|
642
|
+
degraded_count=0,
|
|
643
|
+
reason_codes=["missing_sampled_explanations_status"],
|
|
644
|
+
),
|
|
645
|
+
"author_analysis": analysis.get("author_analysis_status") if isinstance(analysis.get("author_analysis_status"), dict) else _stage_status(
|
|
646
|
+
status="failed",
|
|
647
|
+
ok_count=0,
|
|
648
|
+
failed_count=1,
|
|
649
|
+
degraded_count=0,
|
|
650
|
+
reason_codes=["missing_author_analysis_status"],
|
|
651
|
+
),
|
|
652
|
+
"author_card": _stage_status(
|
|
653
|
+
status="skipped",
|
|
654
|
+
ok_count=0,
|
|
655
|
+
failed_count=0,
|
|
656
|
+
degraded_count=0,
|
|
657
|
+
reason_codes=["pending"],
|
|
658
|
+
),
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
author_analysis_payload = dict(analysis)
|
|
662
|
+
author_analysis_payload["stage_status"] = stage_status
|
|
663
|
+
author_analysis_payload["quality_tier"] = analysis.get("quality_tier")
|
|
664
|
+
author_analysis_payload["sampled_work_ids"] = sampled_work_ids
|
|
665
|
+
|
|
666
|
+
try:
|
|
667
|
+
author_card_write = build_author_card(
|
|
668
|
+
platform=platform,
|
|
669
|
+
profile=profile,
|
|
670
|
+
analysis_payload=author_analysis_payload,
|
|
671
|
+
card_root=card_root,
|
|
672
|
+
storage_config=storage_config,
|
|
673
|
+
write_card=write_card,
|
|
674
|
+
)
|
|
675
|
+
except Exception as error:
|
|
676
|
+
author_card_write = {
|
|
677
|
+
"ok": False,
|
|
678
|
+
"card_type": "author",
|
|
679
|
+
"card_role": "author_card",
|
|
680
|
+
"error_reason": f"author_card_write_failed:{type(error).__name__}:{error}",
|
|
681
|
+
"routing": {
|
|
682
|
+
"card_role": "author_card",
|
|
683
|
+
"route_key": "author",
|
|
684
|
+
"primary_route_parts": "",
|
|
685
|
+
"explicit_override": False,
|
|
686
|
+
"storage_routes_configured": bool(isinstance(storage_config, dict) and isinstance(storage_config.get("storage_routes"), dict)),
|
|
687
|
+
},
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
stage_status["author_card"] = (
|
|
691
|
+
_stage_status(status="full", ok_count=1, failed_count=0, degraded_count=0, reason_codes=[])
|
|
692
|
+
if author_card_write.get("ok")
|
|
693
|
+
else _stage_status(
|
|
694
|
+
status="failed",
|
|
695
|
+
ok_count=0,
|
|
696
|
+
failed_count=1,
|
|
697
|
+
degraded_count=0,
|
|
698
|
+
reason_codes=[str(author_card_write.get("error_reason") or "author_card_write_failed").split(":", 1)[0]],
|
|
699
|
+
failure_kind="runtime",
|
|
700
|
+
)
|
|
701
|
+
)
|
|
702
|
+
stage_status["overall"] = _build_overall_status(stage_status)
|
|
703
|
+
if author_card_write.get("ok"):
|
|
704
|
+
author_analysis_payload["stage_status"] = stage_status
|
|
705
|
+
try:
|
|
706
|
+
author_card_write = build_author_card(
|
|
707
|
+
platform=platform,
|
|
708
|
+
profile=profile,
|
|
709
|
+
analysis_payload=author_analysis_payload,
|
|
710
|
+
card_root=card_root,
|
|
711
|
+
storage_config=storage_config,
|
|
712
|
+
write_card=write_card,
|
|
713
|
+
)
|
|
714
|
+
except Exception:
|
|
715
|
+
pass
|
|
394
716
|
|
|
395
717
|
if progress is not None:
|
|
396
718
|
progress.done(
|
|
@@ -398,8 +720,8 @@ def run_author_home_analysis(
|
|
|
398
720
|
message="author_home card writing finished",
|
|
399
721
|
data={
|
|
400
722
|
"author_card_ok": bool(author_card_write.get("ok")),
|
|
401
|
-
"
|
|
402
|
-
"
|
|
723
|
+
"author_sample_cards_count": int(author_sample_cards.get("count") or 0),
|
|
724
|
+
"sample_work_cards_count": int(sample_work_cards.get("count") or 0),
|
|
403
725
|
},
|
|
404
726
|
)
|
|
405
727
|
|
|
@@ -419,14 +741,23 @@ def run_author_home_analysis(
|
|
|
419
741
|
f"work_queued={work_analysis_stats.get('queued_count', 0)}",
|
|
420
742
|
f"work_failed={work_analysis_stats.get('failed_count', 0)}",
|
|
421
743
|
],
|
|
422
|
-
"confidence":
|
|
423
|
-
|
|
424
|
-
|
|
744
|
+
"confidence": (
|
|
745
|
+
"low"
|
|
746
|
+
if analysis.get("quality_tier") in {"fallback", "failed"} or analysis_missing
|
|
747
|
+
else "medium"
|
|
748
|
+
),
|
|
749
|
+
"error_reason": (
|
|
750
|
+
((stage_status.get("overall") or {}).get("reason_codes") or [None])[0]
|
|
751
|
+
if (stage_status.get("overall") or {}).get("status") == "failed"
|
|
752
|
+
else None
|
|
753
|
+
),
|
|
754
|
+
"missing_fields": adapter_missing + analysis_missing + asr_missing,
|
|
425
755
|
"extract_trace": all_extract_trace,
|
|
426
756
|
"fallback_trace": fallback_trace,
|
|
427
757
|
"request_id": raw.get("request_id"),
|
|
428
758
|
"author_profile": profile,
|
|
429
759
|
"works": works,
|
|
760
|
+
"sampled_work_ids": sampled_work_ids,
|
|
430
761
|
"pagination": {
|
|
431
762
|
**(raw.get("pagination") or {}),
|
|
432
763
|
"total_collected": len(works),
|
|
@@ -436,6 +767,8 @@ def run_author_home_analysis(
|
|
|
436
767
|
"author_analysis_v2": analysis.get("author_analysis_v2") if isinstance(analysis.get("author_analysis_v2"), dict) else {},
|
|
437
768
|
"author_analysis_input_v1": analysis.get("author_analysis_input_v1") if isinstance(analysis.get("author_analysis_input_v1"), dict) else {},
|
|
438
769
|
"analysis_validation": analysis.get("validation") if isinstance(analysis.get("validation"), dict) else {},
|
|
770
|
+
"stage_status": stage_status,
|
|
771
|
+
"quality_tier": analysis.get("quality_tier"),
|
|
439
772
|
"asr_stats": asr_stats,
|
|
440
773
|
"work_analysis": {
|
|
441
774
|
"stats": work_analysis_stats,
|
|
@@ -443,10 +776,16 @@ def run_author_home_analysis(
|
|
|
443
776
|
"artifact_root": work_analysis_bundle.get("artifact_root"),
|
|
444
777
|
"analysis_logic_version": work_analysis_bundle.get("analysis_logic_version"),
|
|
445
778
|
"prompt_contract_hash": work_analysis_bundle.get("prompt_contract_hash"),
|
|
779
|
+
"normalization_version": work_analysis_bundle.get("normalization_version"),
|
|
446
780
|
},
|
|
447
781
|
"card_write": {
|
|
448
782
|
"author": author_card_write,
|
|
449
|
-
"
|
|
783
|
+
"author_sample_cards": author_sample_cards,
|
|
784
|
+
"sample_work_cards": sample_work_cards,
|
|
785
|
+
"works": {
|
|
786
|
+
**author_sample_cards,
|
|
787
|
+
"legacy_alias_of": "author_sample_cards",
|
|
788
|
+
},
|
|
450
789
|
},
|
|
451
790
|
"checkpoint": {
|
|
452
791
|
"last_cursor": ((raw.get("pagination") or {}).get("pages") or [{}])[-1].get("cursor_out") if isinstance((raw.get("pagination") or {}).get("pages"), list) and (raw.get("pagination") or {}).get("pages") else "",
|
|
@@ -471,7 +810,10 @@ def run_author_home_analysis(
|
|
|
471
810
|
input_value=input_value,
|
|
472
811
|
storage_config=storage_config or {},
|
|
473
812
|
persist_output=bool(persist_output),
|
|
813
|
+
card_root=card_root,
|
|
474
814
|
)
|
|
815
|
+
stage_status["persist"] = _build_persist_stage_status(result.get("output_persist") if isinstance(result.get("output_persist"), dict) else {})
|
|
816
|
+
result["stage_status"] = stage_status
|
|
475
817
|
if progress is not None:
|
|
476
818
|
final_event = progress.failed if result.get("error_reason") else progress.done
|
|
477
819
|
final_event(
|
|
@@ -481,7 +823,8 @@ def run_author_home_analysis(
|
|
|
481
823
|
"works_count": len(works),
|
|
482
824
|
"request_id": result.get("request_id"),
|
|
483
825
|
"author_card_ok": bool((result.get("card_write") or {}).get("author", {}).get("ok")),
|
|
484
|
-
"
|
|
826
|
+
"author_sample_cards_count": int(((result.get("card_write") or {}).get("author_sample_cards") or {}).get("count") or 0),
|
|
827
|
+
"sample_work_cards_count": int(((result.get("card_write") or {}).get("sample_work_cards") or {}).get("count") or 0),
|
|
485
828
|
"cache_hit_count": work_analysis_stats.get("cache_hit_count", 0),
|
|
486
829
|
"queued_count": work_analysis_stats.get("queued_count", 0),
|
|
487
830
|
"failed_count": work_analysis_stats.get("failed_count", 0),
|