Weekly QuantEcon Report #34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Weekly QuantEcon Report | |
| on: | |
| schedule: | |
| # Run every Saturday at 9:00 AM UTC | |
| - cron: '0 9 * * 6' | |
| workflow_dispatch: | |
| # Allow manual triggering | |
| permissions: | |
| contents: read | |
| issues: write | |
| jobs: | |
| generate-report: | |
| runs-on: ubuntu-latest | |
| name: Generate Weekly Report | |
| steps: | |
| - name: Generate weekly report | |
| id: report | |
| uses: QuantEcon/action-weekly-report@v2 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| organization: 'QuantEcon' | |
| exclude-repos: 'lecture-.*\.notebooks,test-.*' | |
| track-external-repos: 'executablebooks/sphinx-exercise,executablebooks/sphinx-proof' | |
| highlights: 'true' | |
| llm-summary: 'true' | |
| env: | |
| # Used by --llm-summary to draft 1-sentence theme narratives via Claude. | |
| # If the secret is missing the step warns and still produces highlights | |
| # without narratives (see action-weekly-report docs/highlights.md). | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| - name: Create issue with report and post highlights as first comment | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| // Read the main report content | |
| let reportContent = ''; | |
| try { | |
| reportContent = fs.readFileSync('weekly-report.md', 'utf8'); | |
| console.log(`Read weekly-report.md (${reportContent.length} chars)`); | |
| } catch (error) { | |
| console.error('Failed to read weekly-report.md:', error); | |
| reportContent = 'Error: Unable to load weekly report content.'; | |
| } | |
| // Build the issue title | |
| const now = new Date(); | |
| const weekEnding = now.toLocaleDateString('en-US', { | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric' | |
| }); | |
| const title = `Weekly Report - Week Ending ${weekEnding}`; | |
| // Create the issue | |
| const issue = await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: title, | |
| body: reportContent, | |
| labels: ['weekly-report', 'automated'] | |
| }); | |
| console.log(`Created issue #${issue.data.number}: ${title}`); | |
| // Post the curated highlights as the first comment, if produced. | |
| // The action emits highlights.md when `highlights: true` is set above. | |
| // | |
| // Hardened against: | |
| // - GitHub's 65,536-char comment body limit (truncate with a pointer | |
| // to the uploaded artifact) | |
| // - transient API failures (log + continue rather than fail the | |
| // workflow — the issue itself has already been created above, so | |
| // the report isn't lost even if the comment can't be posted) | |
| const COMMENT_BODY_LIMIT = 65536; | |
| if (fs.existsSync('highlights.md')) { | |
| // Read + truncate inside its own try/catch — existsSync doesn't | |
| // guarantee the read succeeds (permissions, IO), and we want any | |
| // such failure to behave like "file not produced" rather than | |
| // crash the workflow. | |
| let highlights; | |
| try { | |
| highlights = fs.readFileSync('highlights.md', 'utf8'); | |
| } catch (readErr) { | |
| console.error(`Failed to read highlights.md: ${readErr.message || readErr}`); | |
| console.error('Skipping highlights comment. The report issue was still created.'); | |
| highlights = null; | |
| } | |
| if (highlights !== null) { | |
| const originalLen = highlights.length; | |
| if (highlights.length > COMMENT_BODY_LIMIT) { | |
| const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | |
| const footer = `\n\n---\n\n_Highlights truncated to fit GitHub's 65,536-char comment limit. Full content available in the workflow artifact: [weekly-report (run #${context.runId})](${runUrl})._\n`; | |
| highlights = highlights.slice(0, COMMENT_BODY_LIMIT - footer.length) + footer; | |
| console.log(`Truncated highlights from ${originalLen} to ${highlights.length} chars`); | |
| } | |
| try { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.data.number, | |
| body: highlights, | |
| }); | |
| console.log(`Posted highlights.md (${highlights.length} chars) as first comment`); | |
| } catch (err) { | |
| // Don't fail the run — the issue is created and the artifact is | |
| // uploaded, so the highlights are recoverable manually. | |
| // Octokit errors carry status + response.data; log them so | |
| // future debugging isn't limited to err.message (which is | |
| // often terse, e.g. "Validation Failed"). | |
| const status = err.status ? ` [HTTP ${err.status}]` : ''; | |
| const message = err.message || String(err); | |
| const responseDetail = err.response?.data ? ` response=${JSON.stringify(err.response.data)}` : ''; | |
| console.error(`Failed to post highlights comment on issue #${issue.data.number}${status}: ${message}${responseDetail}`); | |
| console.error('The report issue was still created. highlights.md is available in the workflow artifact.'); | |
| } | |
| } | |
| } else { | |
| console.log('highlights.md not produced — skipping highlights comment'); | |
| } | |
| - name: Upload report artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: weekly-report | |
| # Bundle every artifact the action produces so we can re-run | |
| # tools/highlights.py against weekly-report-data.json offline if we | |
| # ever want to retune themes or compare LLM models. | |
| path: | | |
| weekly-report.md | |
| weekly-report-data.json | |
| highlights.md | |
| retention-days: 90 |