Claude-skill-registry earnings-orchestrator
Master orchestrator for batch earnings analysis
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/earnings-orchestrator" ~/.claude/skills/majiayu000-claude-skill-registry-earnings-orchestrator && rm -rf "$T"
skills/data/earnings-orchestrator/SKILL.mdEarnings Orchestrator
Input
$ARGUMENTS = TICKER
- TICKER: Company ticker (required)
Task - MUST COMPLETE ALL STEPS
Step 0: Record Start Time
echo "=== START: $(date '+%Y-%m-%d %H:%M:%S') ==="
Step 1: Get Earnings Data
source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_earnings.py {TICKER}
Output columns: accession|date|fiscal_year|fiscal_quarter|market_session|daily_stock|daily_adj|sector_adj|industry_adj|trailing_vol|vol_days|vol_status|fye_month
Parse: Extract E1 (first data row after header), E2 (second data row after header). The script returns data sorted oldest-to-newest, so E1 is the OLDEST quarter, E2 is the second oldest. Single-quarter mode: only process one quarter per run (E1 unless fully cached). Note
trailing_vol and fye_month for each. The fye_month (1-12) indicates the company's fiscal year end month (e.g., 9=September for Apple, 12=December for most companies).
If ERROR returned: Stop and report error to user.
Step 1b: Check Processing Cache
Check both
earnings-analysis/news_processed.csv and earnings-analysis/guidance_processed.csv for {TICKER}.
News cache: (format:
ticker|quarter|fiscal_year|processed_date)
- Find row where
ANDticker={TICKER}
ANDquarter={E1.fiscal_quarter}fiscal_year=FY{E1.fiscal_year} - If row exists →
Q1_NEWS_CACHED=true - Repeat check for Q2 →
Q2_NEWS_CACHED
Guidance cache: (format:
ticker|quarter|fiscal_year|processed_date)
- Find row where
ANDticker={TICKER}
ANDquarter={E1.fiscal_quarter}fiscal_year=FY{E1.fiscal_year} - If row exists →
Q1_GUIDANCE_CACHED=true - Repeat check for Q2 →
Q2_GUIDANCE_CACHED
Skip logic for each quarter:
| Q1_NEWS_CACHED | Q1_GUIDANCE_CACHED | Action |
|---|---|---|
| false | false | Run full Steps 2, 3, 3b for Q1 (both tracks) |
| true | false | Skip news in Steps 2, 3, 3b; run guidance only |
| false | true | Skip guidance in Steps 2, 3, 3b; run news only |
| true | true | Skip Steps 2, 3, 3b entirely for Q1 → go to Step 4 |
Same logic applies to Q2 with Steps 4, 4b.
Step 1c: Select Target Quarter (Single-Quarter Mode)
Goal: Process only one quarter per run.
Rules:
- If Q1 is NOT fully cached (Q1_NEWS_CACHED=false OR Q1_GUIDANCE_CACHED=false) → set
and process ONLY Q1.TARGET=Q1 - Else if Q2 is NOT fully cached → set
and process ONLY Q2.TARGET=Q2 - Else → STOP (nothing to do).
This ensures the run advances to the next quarter only after the previous quarter is fully processed.
If TARGET=Q1, do NOT proceed past Step 3b. Stop the run after Q1 save.
Step 2: Discovery for Q1 (News + Guidance)
Only run Step 2–3b if TARGET=Q1. If TARGET=Q2, skip directly to Step 4.
Calculate:
= E1 date minus 3 months (no further adjustment; scripts handle empty ranges)START
= E1 date (just the date part, e.g., 2024-02-01)END
Run discovery scripts (only for non-cached tracks; sequential OK):
# News discovery (SKIP if Q1 news cached) source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_significant_moves.py {TICKER} {START} {END} {E1.trailing_vol} # Guidance discovery - all 5 (SKIP ALL if Q1 guidance cached) source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_8k_filings_range.py {TICKER} {START} {END} source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_10k_filings_range.py {TICKER} {START} {END} source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_10q_filings_range.py {TICKER} {START} {END} source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_transcript_range.py {TICKER} {START} {END} source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_guidance_news_range.py {TICKER} {START} {END}
Cache-aware execution: (based on Step 1b flags)
- If
→ skipQ1_NEWS_CACHED=trueget_significant_moves.py - If
→ skip all 5 guidance discovery scriptsQ1_GUIDANCE_CACHED=true - If both cached → skip Steps 2, 3, 3b entirely for Q1
Parse news results:
output columns: date|daily_stock|daily_macro|daily_adjget_significant_moves.py- Parse: List of dates with significant moves
Parse guidance results (content-level sources): All 5 discovery scripts return the SAME format:
report_id|date|source_type|source_key
→ 8-K content (source_type: exhibit, section, filing_text)get_8k_filings_range.py
→ 10-K content (source_type: exhibit, section, filing_text, financial_stmt, xbrl)get_10k_filings_range.py
→ 10-Q content (source_type: exhibit, section, filing_text, financial_stmt, xbrl)get_10q_filings_range.py
→ Transcripts (source_type: transcript, source_key: full)get_transcript_range.py
→ News (source_type: news, source_key: full)get_guidance_news_range.py
Combine all guidance sources into a single list. Each line is one content source to process. Parse uniformly - no special handling needed.
If OK|NO_MOVES returned: No significant moves for Q1 news, skip news tasks but still process guidance if sources found.
Step 3: Foreground Analysis for Q1 (News + Guidance)
Phase 1: Create tasks upfront (only for non-cached tracks)
NEWS TASKS (with blockedBy dependencies) - SKIP if
:
For EACH significant date from Step 2, create all 4 tasks with dependency chain:Q1_NEWS_CACHED=true
-
Create BZ task via TaskCreate:
:subject
(e.g., "BZ-Q4_FY2022 NOG 2023-01-03")"BZ-{QUARTER} {TICKER} {DATE}"
:description"pending"
:activeForm"Analyzing {TICKER} {DATE}"- Note the task ID as
BZ_ID
-
Create WEB task via TaskCreate:
:subject"WEB-{QUARTER} {TICKER} {DATE}"
:description"{TICKER} {DATE} {DAILY_STOCK} {DAILY_ADJ}"
:activeForm"Web research {TICKER} {DATE}"- Then call TaskUpdate with
addBlockedBy: ["{BZ_ID}"] - Note the task ID as
WEB_ID
-
Create PPX task via TaskCreate:
:subject"PPX-{QUARTER} {TICKER} {DATE}"
:description"{TICKER} {DATE} {DAILY_STOCK} {DAILY_ADJ}"
:activeForm"Perplexity research {TICKER} {DATE}"- Then call TaskUpdate with
addBlockedBy: ["{WEB_ID}"] - Note the task ID as
PPX_ID
-
Create JUDGE task via TaskCreate:
:subject"JUDGE-{QUARTER} {TICKER} {DATE}"
:description"pending"
:activeForm"Validating {TICKER} {DATE}"- Then call TaskUpdate with
addBlockedBy: ["{PPX_ID}"] - Note the task ID as
JUDGE_ID
GUIDANCE TASKS (NO dependencies; sequential OK) - SKIP if
:Q1_GUIDANCE_CACHED=true
For EACH content source line from guidance discovery (format:
report_id|date|source_type|source_key), create a task:
:subject"GX-{QUARTER} {TICKER} {REPORT_ID} {SOURCE_TYPE}"- Pattern:
where id format varies by source typeGX-{quarter} {ticker} {id} {type}
- Pattern:
:description
(store for agent prompt)"{REPORT_ID}|{SOURCE_TYPE}|{SOURCE_KEY}"
:activeForm"Extracting guidance from {SOURCE_TYPE}"
All 7 source types use the same format - no special handling needed for transcripts or news.
Phase 1.5: Write manifest (Q1) Create directory (if needed):
earnings-analysis/Companies/{TICKER}/manifests/
Also create output directories:
- Guidance:
earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}/gx/ - News judge:
earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}/judge/
Write manifest file:
earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}.json
Format:
{ "ticker": "{TICKER}", "quarter": "{E1.fiscal_quarter}_FY{E1.fiscal_year}", "generated_at": "{ISO timestamp}", "news": { "expected_judge": {N}, "judge_tasks": [ {"id": "{JUDGE_ID}", "date": "{DATE}"}, ... ] }, "guidance": { "expected": {M}, "tasks": [ {"id": "{GX_ID}", "report_id": "{REPORT_ID}", "source_type": "{SOURCE_TYPE}", "source_key": "{SOURCE_KEY}"}, ... ] } }
If a track is cached, set its expected count to 0 and tasks to []. This manifest is the source of truth for validation and aggregation.
Phase 2: Spawn agents (only for non-cached tracks, foreground parallel batches)
⚠️ CRITICAL: ALL AGENTS MUST RUN IN FOREGROUND ⚠️
- Do NOT use
on ANY Task tool callrun_in_background: true - Foreground Task calls can run in parallel within the same response. Spawn in batches of up to 50 Task calls per response, then proceed to the next batch.
- Background mode breaks result collection
⚠️ CRITICAL: DO NOT SPAWN GUIDANCE-EXTRACT WITHOUT A TASK_ID ⚠️
- Every guidance source MUST be TaskCreate'd first
- TASK_ID must be the TaskCreate ID for that source (never invent or omit)
- If a TASK_ID is missing, STOP and report the error before spawning
NEWS: Spawn BZ agents for each significant date - SKIP if
:Q1_NEWS_CACHED=true
subagent_type: "news-driver-bz" description: "BZ news {TICKER} {DATE}" prompt: "{TICKER} {DATE} {DAILY_STOCK} {DAILY_ADJ} TASK_ID={BZ_ID} WEB_TASK_ID={WEB_ID} PPX_TASK_ID={PPX_ID} JUDGE_TASK_ID={JUDGE_ID} QUARTER={E1.fiscal_quarter}_FY{E1.fiscal_year}" run_in_background: false # ALWAYS foreground
GUIDANCE: Spawn guidance-extract agents - SKIP if
(one per content source, foreground parallel batches of up to 50):Q1_GUIDANCE_CACHED=true
For EACH guidance task, read the description to get
{REPORT_ID}|{SOURCE_TYPE}|{SOURCE_KEY}, then spawn:
subagent_type: "guidance-extract" description: "Guidance {TICKER} {SOURCE_TYPE}" prompt: "{TICKER} {REPORT_ID} {SOURCE_TYPE} {SOURCE_KEY} {QUARTER} FYE={fye_month} TASK_ID={TASK_ID}" run_in_background: false # ALWAYS foreground
Key variations in prompt format:
- Filing sources:
{TICKER} {accession} {source_type} {source_key} {QUARTER} FYE={fye_month} TASK_ID={id} - Transcript:
{TICKER} {transcript_id} transcript full {QUARTER} FYE={fye_month} TASK_ID={id} - News:
{TICKER} {news_id} news full {QUARTER} FYE={fye_month} TASK_ID={id}
The
fye_month comes from E1/E2 data (get_earnings.py output). Pass the same value for all guidance tasks of that company.
IMPORTANT:
- Create tasks only for non-cached tracks, THEN spawn agents for those tracks in foreground-parallel batches (≤50)
- If only news cached → create + spawn guidance agents only
- If only guidance cached → create + spawn news agents only
- BZ agents mark WEB+PPX as SKIPPED if they find answer (external_research=false)
- Guidance agents have NO dependencies - they complete independently
- After spawning all agents, immediately enter Phase 3 polling loop
- Do NOT proceed to Phase 4 until all relevant tasks are completed
Phase 3: Escalation loop (foreground) - SKIP if Q1_NEWS_CACHED=true
If
Q1_NEWS_CACHED=false, immediately after spawning BZ agents, enter this loop:
⚠️ REMINDER: ALL Task tool calls MUST have
⚠️run_in_background: false
WHILE any Q1 tasks (BZ-*, WEB-*, PPX-*, JUDGE-*) are pending or in_progress: 1. Check TaskList for WEB-{QUARTER} {TICKER} tasks that are: - status = "pending" AND blockedBy is empty (auto-unblocked when BZ completed) - NOT already spawned → For each such WEB task: - Get task via TaskGet to read description: "{TICKER} {DATE} {DAILY_STOCK} {DAILY_ADJ}" - Extract QUARTER from task subject - Find corresponding PPX and JUDGE task IDs from TaskList - Spawn (FOREGROUND ONLY): subagent_type: "news-driver-web" prompt: "{TICKER} {DATE} {DAILY_STOCK} {DAILY_ADJ} TASK_ID={WEB_ID} PPX_TASK_ID={PPX_ID} JUDGE_TASK_ID={JUDGE_ID} QUARTER={QUARTER}" run_in_background: false → WEB agents mark PPX as SKIPPED if confidence >= 50 2. Check TaskList for PPX-{QUARTER} {TICKER} tasks that are: - status = "pending" AND blockedBy is empty (auto-unblocked when WEB completed) - NOT already spawned → For each such PPX task: - Get task via TaskGet to read description - Extract QUARTER from task subject - Find corresponding JUDGE task ID from TaskList - Spawn (FOREGROUND ONLY): subagent_type: "news-driver-ppx" prompt: "{TICKER} {DATE} {DAILY_STOCK} {DAILY_ADJ} TASK_ID={PPX_ID} JUDGE_TASK_ID={JUDGE_ID} QUARTER={QUARTER}" run_in_background: false → PPX agents always update JUDGE with result (final tier) 3. Check TaskList for JUDGE-{QUARTER} {TICKER} tasks that are: - status = "pending" AND blockedBy is empty (auto-unblocked when PPX completed or skipped) - NOT already spawned - description starts with "READY:" (has result to validate) → For each such JUDGE task: - Spawn (FOREGROUND ONLY): subagent_type: "news-driver-judge" prompt: "TASK_ID={JUDGE_ID}" run_in_background: false → JUDGE agents validate and update task with final confidence 4. Brief pause (2-3 seconds), then repeat END WHILE
Track which task IDs you've already spawned agents for to avoid duplicates.
Note on SKIPPED tasks: When BZ or WEB finds a confident answer, they mark downstream tasks as "completed" with description="SKIPPED: {tier} found answer". This auto-unblocks the next task in chain (JUDGE for BZ skip, JUDGE for WEB skip).
Phase 4: Collect all results
NEWS RESULTS: When all Q1 news tasks (BZ-, WEB-, PPX-, JUDGE-) are completed, collect results from per-task files written by the judge subagents:
earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}/judge/{TASK_ID}.tsv
Each file contains a single 12-field line (no header). Do not use TaskGet for news results.
Note: Each date has exactly one JUDGE task with the final validated result. BZ/WEB/PPX tasks contain intermediate results.
GUIDANCE RESULTS: When all Q1 guidance tasks (GX-*) are completed, collect results from per-task files written by the guidance subagents:
earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}/gx/{TASK_ID}.tsv
Each file contains newline-delimited guidance entries in the 18-field format below, or
NO_GUIDANCE|{source_type}|{source_key}, or ERROR|.... Do not use TaskGet for guidance.
period_type|fiscal_year|fiscal_quarter|segment|metric|low|mid|high|unit|basis|derivation|qualitative|source_type|source_id|source_key|given_date|section|quote
Field definitions:
:period_type
,quarter
,annual
, orhalflong-range
: e.g.,fiscal_year2025
:fiscal_quarter
,1
,2
,3
, or4
for annual.
:segment
(default), or specific segment likeTotal
,Services
,iPhoneAWS
: Normalized name likemetric
,Revenue
,EPSGross Margin
,low
,mid
: Numbers orhigh
for qualitative guidance.
:unit
,%
,USD
,B USD
, etc.% YoY
:basis
,GAAP
,non-GAAP
, etc.as-reported
:derivation
,explicit
(mid derived),calculated
, orpoint
(qualitative only)implied
: Non-numeric guidance text (e.g., "double digits") orqualitative.
,source_type
,source_id
: Source identificationsource_key
: When guidance was issued (YYYY-MM-DD)given_date
: Location in source (e.g., "CFO prepared remarks", "Outlook section")section
: Exact text with pipes replaced by ¦quote
Or
NO_GUIDANCE|{source_type}|{source_key} if no guidance found in that source.
Note: Guidance tasks have no dependencies and complete independently of news tasks.
Phase 4.5: Validation gate (MANDATORY, before Step 3b)
Fresh session rule (context saver): Run Phase 4.5 + saving results in a new session using the same
CLAUDE_CODE_TASK_LIST_ID. This avoids carrying the full spawn-phase context into validation.
Load the Q1 manifest from:
earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}.json
Validation rules:
- For each
ID:news.judge_tasks- Read file:
earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}/judge/{TASK_ID}.tsv - If file missing: mark missing
- Must be a single 12-field pipe-delimited line
- Read file:
- For each
ID:guidance.tasks- Read file:
earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}/gx/{TASK_ID}.tsv - If file missing: mark missing
- For each non-empty line:
- If line starts with
→ skipNO_GUIDANCE| - If line starts with
→ invalid (retry)ERROR| - Else must have 18 fields
- If line starts with
- If file is empty/invalid: log it, add to retry file, and do NOT mark processed.
- Read file:
Counting rule (no headers):
- Per-task files (
andgx/*.tsv
) never include headers.judge/*.tsv - If you sanity-check counts, count only
files and compare to manifest IDs.*.tsv - Do not add +1 for headers or count directories.
Hard stop rule (fail-closed):
- If any expected guidance file is missing, or any required judge file is missing, STOP immediately.
- Write a retry file (if needed) and do NOT write
,guidance.csv
, or processed caches..ok - Do NOT attempt to reconstruct results from memory/Neo4j.
Best-effort mode (deterministic):
- If any task is missing or invalid, do NOT stop the run (unless the hard stop rule above applies).
- Write a retry file listing only the failed task IDs:
Format:earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}.retry.json{"failed":[{"id":"123","reason":"invalid_format"}, ...]} - Proceed to Step 3b and write all valid rows.
- Do NOT update processed caches unless there are zero failed tasks.
Only create the
.ok marker when there are zero failed tasks. Processed CSV updates require the .ok marker; the retry file never unlocks processing.
Use the Write tool (not Bash redirection) for .ok and processed CSVs so hooks can enforce guards.
If no tasks are missing/invalid:
- Create marker file:
(This marker is required to unlock processed CSV updates.)earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}.ok
Step 3b: Save Q1 Results
NEWS RESULTS: Write all valid rows. If retry file exists, do NOT update processed cache. Source of truth: per-task files in
earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}/judge/.
For each judge task ID in the manifest, read {TASK_ID}.tsv and append the 12-field line.
- Create directory if needed:
earnings-analysis/Companies/{TICKER}/ - Append Q1 results to
:earnings-analysis/Companies/{TICKER}/news.csv- Add
column with valuequarter
(e.g.,{E1.fiscal_quarter}_FY{E1.fiscal_year}
)Q1_FY2024 - Format:
quarter|date|news_id|driver|attr_confidence|pred_confidence|daily_stock|daily_adj|market_session|source|external_research|source_pub_date|judge_notes - Create file with header if it doesn't exist
- Add
- Update
:earnings-analysis/news_processed.csv- Format:
ticker|quarter|fiscal_year|processed_date - Append row:
{TICKER}|{E1.fiscal_quarter}|FY{E1.fiscal_year}|{today YYYY-MM-DD} - Create file with header if it doesn't exist
- Format:
GUIDANCE RESULTS: Write all valid rows. If retry file exists, do NOT update processed cache. Source of truth: per-task files in
earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}/gx/.
For each guidance task ID in the manifest, read {TASK_ID}.tsv and append any valid lines (skip NO_GUIDANCE|...).
4. Append Q1 guidance to earnings-analysis/Companies/{TICKER}/guidance.csv:
- Add
column with valuequarter{E1.fiscal_quarter}_FY{E1.fiscal_year} - Format (19 fields):
quarter|period_type|fiscal_year|fiscal_quarter|segment|metric|low|mid|high|unit|basis|derivation|qualitative|source_type|source_id|source_key|given_date|section|quote
here is numeric (fiscal_quarter
) from guidance-extract; do not convert to1-4
formatQ1- Skip lines that start with
NO_GUIDANCE - Create file with header if it doesn't exist
- Update
:earnings-analysis/guidance_processed.csv- Format:
ticker|quarter|fiscal_year|processed_date - Append row:
{TICKER}|{E1.fiscal_quarter}|FY{E1.fiscal_year}|{today YYYY-MM-DD} - Create file with header if it doesn't exist
- Format:
Step 4: Foreground Analysis for Q2 (News + Guidance) — ONLY IF TARGET=Q2
Only run Step 4–4b if TARGET=Q2.
Calculate:
= E1 date + 1 day (exclude E1 earnings reaction)START
= E2 date (exclusive, excludes E2 earnings reaction). Scripts already use [start,end), so pass E2 as-is (do not subtract a day).END
Run discovery scripts (only for non-cached tracks; sequential OK):
# News discovery (SKIP if Q2 news cached) source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_significant_moves.py {TICKER} {START} {END} {E2.trailing_vol} # Guidance discovery - all 5 (SKIP ALL if Q2 guidance cached) source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_8k_filings_range.py {TICKER} {START} {END} source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_10k_filings_range.py {TICKER} {START} {END} source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_10q_filings_range.py {TICKER} {START} {END} source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_transcript_range.py {TICKER} {START} {END} source /home/faisal/EventMarketDB/venv/bin/activate && python /home/faisal/EventMarketDB/scripts/earnings/get_guidance_news_range.py {TICKER} {START} {END}
Cache-aware execution: (based on Step 1b flags)
- If
→ skipQ2_NEWS_CACHED=trueget_significant_moves.py - If
→ skip all 5 guidance discovery scriptsQ2_GUIDANCE_CACHED=true - If both cached → skip Steps 4, 4b entirely for Q2
Then follow the same foreground batch pattern as Step 3:
- Phase 1: Create ALL tasks (news with blockedBy + guidance without dependencies)
- Phase 1.5: Write manifest for Q2 (create gx/judge output dirs)
- Phase 2: Spawn BZ agents AND guidance agents in foreground-parallel batches (≤50)
- Phase 3: Escalation loop (foreground) - spawn WEB/PPX/JUDGE as they auto-unblock
- Phase 4: Collect all Q2 results (news from JUDGE-, guidance from GX-)
- Phase 4.5: Validation gate before saving results
Use
QUARTER={E2.fiscal_quarter}_FY{E2.fiscal_year} for all Q2 tasks.
Step 4b: Save Q2 Results
Same as Step 3b but for Q2:
NEWS RESULTS:
- Append to
withearnings-analysis/Companies/{TICKER}/news.csvquarter={E2.fiscal_quarter}_FY{E2.fiscal_year} - Append to
:news_processed.csv{TICKER}|{E2.fiscal_quarter}|FY{E2.fiscal_year}|{today YYYY-MM-DD}
GUIDANCE RESULTS: 3. Append to
earnings-analysis/Companies/{TICKER}/guidance.csv with quarter={E2.fiscal_quarter}_FY{E2.fiscal_year}
- Source of truth:
earnings-analysis/Companies/{TICKER}/manifests/{QUARTER}/gx/{TASK_ID}.tsv
- Append to
:guidance_processed.csv{TICKER}|{E2.fiscal_quarter}|FY{E2.fiscal_year}|{today YYYY-MM-DD}
Step 5: Return Combined Results
=== EARNINGS ORCHESTRATOR: {TICKER} === --- EARNINGS DATA --- E1: {accession} | {date} | FY{fiscal_year} {fiscal_quarter} | {daily_adj}% adj | vol={trailing_vol}% ({vol_days}d) {vol_status} E2: {accession} | {date} | FY{fiscal_year} {fiscal_quarter} | {daily_adj}% adj | vol={trailing_vol}% ({vol_days}d) {vol_status} ... --- Q1 NEWS ANALYSIS ({START} to {E1}) --- Filter: |stock|>=4%, |adj|>=max(2×{trailing_vol}%,3%) Significant dates: {count} date|news_id|driver|attr_confidence|pred_confidence|daily_stock|daily_adj|market_session|source|external_research|source_pub_date|judge_notes ... --- Q1 GUIDANCE EXTRACTION --- Content sources processed: {total_sources} - exhibits: {exhibit_count} - sections: {section_count} - financial_stmt: {financial_stmt_count} - xbrl: {xbrl_count} - transcripts: {transcript_count} - news: {news_count} Guidance entries found: {guidance_count} quarter|period_type|fiscal_year|fiscal_quarter|segment|metric|low|mid|high|unit|basis|derivation|qualitative|source_type|source_id|source_key|given_date|section|quote ... --- Q2 NEWS ANALYSIS ({E1} to {E2}) --- Filter: |stock|>=4%, |adj|>=max(2×{trailing_vol}%,3%) Significant dates: {count} date|news_id|driver|attr_confidence|pred_confidence|daily_stock|daily_adj|market_session|source|external_research|source_pub_date|judge_notes ... --- Q2 GUIDANCE EXTRACTION --- Content sources processed: {total_sources} - exhibits: {exhibit_count} - sections: {section_count} - financial_stmt: {financial_stmt_count} - xbrl: {xbrl_count} - transcripts: {transcript_count} - news: {news_count} Guidance entries found: {guidance_count} quarter|period_type|fiscal_year|fiscal_quarter|segment|metric|low|mid|high|unit|basis|derivation|qualitative|source_type|source_id|source_key|given_date|section|quote ... --- NEWS SUMMARY --- Total dates analyzed: {N} Explained by Benzinga: {B} Explained by WebSearch: {W} Explained by Perplexity: {P} Still unknown (confidence=0): {U} Validated by Judge: {J} --- GUIDANCE SUMMARY --- Total content sources processed: {total_sources} - exhibits: {exhibit_count} - sections: {section_count} - financial_stmt: {financial_stmt_count} - xbrl: {xbrl_count} - transcripts: {transcript_count} - news: {news_count} Total guidance entries found: {total_guidance} === COMPLETE ===
Step 6: Signal Completion (Auto-Triggers Thinking Build)
echo "=== ORCHESTRATOR_COMPLETE {TICKER} $(date '+%Y-%m-%d %H:%M:%S') ==="
This command triggers the PostToolUse hook which automatically runs:
build-news-thinking.py --ticker {TICKER}build-guidance-thinking.py --ticker {TICKER}
Thinking files appear in Obsidian at
Companies/{TICKER}/thinking/{QUARTER}/ without explicit script calls.
Rules
- NEVER use run_in_background - ALL Task tool calls MUST run in foreground. Do NOT set
. Wait for each batch to complete before proceeding. This is critical for proper result collection.run_in_background: true - Create tasks first; spawn in foreground-parallel batches (≤50) - Issue up to 50 Task calls per response; then proceed to the next batch.
- Full row replacement - When a later tier returns a result, use its COMPLETE output. PPX replaces WEB, WEB replaces BZ. Never mix fields across tiers. Judge outputs 12-field line.
- Always run get_earnings.py first - provides trailing_vol for each quarter
- Skip if done - check news_processed.csv, skip quarters already processed
- No background mode - use foreground-parallel batches (≤50); do NOT set
.run_in_background: true - Q1 complete before Q2 - finish all 4 tiers (BZ → WEB → PPX → JUDGE) + save for Q1, then Q2
- Processed cache format - always write
asfiscal_year
(e.g., FY2023), never plain yearFY{YYYY} - Extract date only - E1 date "2024-02-01T16:30:33-05:00" → use "2024-02-01"
- Preserve news_id EXACTLY - Copy URLs verbatim. NEVER shorten, summarize, or create short IDs. If sub-agent returns a URL, save the full URL exactly as returned.
- Pass through raw output - don't summarize or lose data
- Always save results - append to news.csv/guidance.csv; update processed CSVs only when
exists (no retry file).ok - Verify before marking processed - Confirm data rows were appended to CSV before updating processed.csv. If no results to save, do not mark as processed.
Error Handling
Script errors return structured format:
ERROR|CODE|MESSAGE|HINT
If any script returns ERROR:
- Log the error in output
- Try to continue with remaining steps if possible
- Report all errors in summary
Example
Input:
AAPL
Flow:
- get_earnings.py AAPL → E1=2024-02-01 (Q1_FY2024, vol=0.90), E2=2024-05-02 (Q2_FY2024, vol=0.99)
- Check news_processed.csv → no row for AAPL|Q1|FY2024 → process Q1
- get_significant_moves.py AAPL 2023-11-01 2024-02-01 0.90 → internally: |stock|>=4%, |adj|>=max(2×0.90,3)=3%
- Spawn news-driver-bz for each significant date → some explained (create JUDGE), some create WEB tasks
- Spawn news-driver-web for each WEB task → some explained (create JUDGE), some create PPX tasks
- Spawn news-driver-ppx for each PPX task → all create JUDGE tasks
- Spawn news-driver-judge for each JUDGE task → validates and returns final confidence
- Save Q1 to Companies/AAPL/news.csv (from JUDGE results), mark Q1_FY2024 done
- Check news_processed.csv → row exists for AAPL|Q2|FY2024 → skip Q2
- Return results (Q1 only, Q2 was cached)