riksdagsmonitor 0.8.14 → 0.8.17
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/dist/lib/cia/dashboard-init.d.ts +26 -0
- package/dist/lib/cia/dashboard-init.d.ts.map +1 -0
- package/dist/lib/cia/dashboard-init.js +108 -0
- package/dist/lib/cia/dashboard-init.js.map +1 -0
- package/dist/lib/cia/data-loader.d.ts +408 -0
- package/dist/lib/cia/data-loader.d.ts.map +1 -0
- package/dist/lib/cia/data-loader.js +867 -0
- package/dist/lib/cia/data-loader.js.map +1 -0
- package/dist/lib/cia/election-predictions.d.ts +78 -0
- package/dist/lib/cia/election-predictions.d.ts.map +1 -0
- package/dist/lib/cia/election-predictions.js +259 -0
- package/dist/lib/cia/election-predictions.js.map +1 -0
- package/dist/lib/cia/i18n-translations.d.ts +111 -0
- package/dist/lib/cia/i18n-translations.d.ts.map +1 -0
- package/dist/lib/cia/i18n-translations.js +619 -0
- package/dist/lib/cia/i18n-translations.js.map +1 -0
- package/dist/lib/cia/visualizations.d.ts +64 -0
- package/dist/lib/cia/visualizations.d.ts.map +1 -0
- package/dist/lib/cia/visualizations.js +662 -0
- package/dist/lib/cia/visualizations.js.map +1 -0
- package/dist/lib/dashboards/anomaly-detection.d.ts +44 -0
- package/dist/lib/dashboards/anomaly-detection.d.ts.map +1 -0
- package/dist/lib/dashboards/anomaly-detection.js +1017 -0
- package/dist/lib/dashboards/anomaly-detection.js.map +1 -0
- package/dist/lib/dashboards/coalition-dashboard.d.ts +36 -0
- package/dist/lib/dashboards/coalition-dashboard.d.ts.map +1 -0
- package/dist/lib/dashboards/coalition-dashboard.js +645 -0
- package/dist/lib/dashboards/coalition-dashboard.js.map +1 -0
- package/dist/lib/dashboards/coalition-loader.d.ts +43 -0
- package/dist/lib/dashboards/coalition-loader.d.ts.map +1 -0
- package/dist/lib/dashboards/coalition-loader.js +701 -0
- package/dist/lib/dashboards/coalition-loader.js.map +1 -0
- package/dist/lib/dashboards/committees-dashboard.d.ts +23 -0
- package/dist/lib/dashboards/committees-dashboard.d.ts.map +1 -0
- package/dist/lib/dashboards/committees-dashboard.js +886 -0
- package/dist/lib/dashboards/committees-dashboard.js.map +1 -0
- package/dist/lib/dashboards/election-cycle.d.ts +62 -0
- package/dist/lib/dashboards/election-cycle.d.ts.map +1 -0
- package/dist/lib/dashboards/election-cycle.js +484 -0
- package/dist/lib/dashboards/election-cycle.js.map +1 -0
- package/dist/lib/dashboards/ministry-dashboard.d.ts +46 -0
- package/dist/lib/dashboards/ministry-dashboard.d.ts.map +1 -0
- package/dist/lib/dashboards/ministry-dashboard.js +1114 -0
- package/dist/lib/dashboards/ministry-dashboard.js.map +1 -0
- package/dist/lib/dashboards/party-dashboard.d.ts +56 -0
- package/dist/lib/dashboards/party-dashboard.d.ts.map +1 -0
- package/dist/lib/dashboards/party-dashboard.js +981 -0
- package/dist/lib/dashboards/party-dashboard.js.map +1 -0
- package/dist/lib/dashboards/politician-dashboard.d.ts +41 -0
- package/dist/lib/dashboards/politician-dashboard.d.ts.map +1 -0
- package/dist/lib/dashboards/politician-dashboard.js +611 -0
- package/dist/lib/dashboards/politician-dashboard.js.map +1 -0
- package/dist/lib/dashboards/pre-election.d.ts +64 -0
- package/dist/lib/dashboards/pre-election.d.ts.map +1 -0
- package/dist/lib/dashboards/pre-election.js +604 -0
- package/dist/lib/dashboards/pre-election.js.map +1 -0
- package/dist/lib/dashboards/risk-dashboard.d.ts +37 -0
- package/dist/lib/dashboards/risk-dashboard.d.ts.map +1 -0
- package/dist/lib/dashboards/risk-dashboard.js +863 -0
- package/dist/lib/dashboards/risk-dashboard.js.map +1 -0
- package/dist/lib/dashboards/seasonal-patterns.d.ts +24 -0
- package/dist/lib/dashboards/seasonal-patterns.d.ts.map +1 -0
- package/dist/lib/dashboards/seasonal-patterns.js +535 -0
- package/dist/lib/dashboards/seasonal-patterns.js.map +1 -0
- package/dist/lib/dashboards/stats-loader.d.ts +35 -0
- package/dist/lib/dashboards/stats-loader.d.ts.map +1 -0
- package/dist/lib/dashboards/stats-loader.js +145 -0
- package/dist/lib/dashboards/stats-loader.js.map +1 -0
- package/dist/lib/{chart-factory.d.ts → shared/chart-factory.d.ts} +9 -2
- package/dist/lib/shared/chart-factory.d.ts.map +1 -0
- package/dist/lib/{chart-factory.js → shared/chart-factory.js} +9 -2
- package/dist/lib/shared/chart-factory.js.map +1 -0
- package/dist/lib/shared/data-loader.d.ts.map +1 -0
- package/dist/lib/shared/data-loader.js.map +1 -0
- package/dist/lib/shared/dom-utils.d.ts.map +1 -0
- package/dist/lib/shared/dom-utils.js.map +1 -0
- package/dist/lib/shared/error-boundary.d.ts.map +1 -0
- package/dist/lib/shared/error-boundary.js.map +1 -0
- package/dist/lib/shared/fallback-ui.d.ts.map +1 -0
- package/dist/lib/shared/fallback-ui.js.map +1 -0
- package/dist/lib/shared/index.d.ts.map +1 -0
- package/dist/lib/shared/index.js.map +1 -0
- package/dist/lib/shared/logger.d.ts.map +1 -0
- package/dist/lib/shared/logger.js.map +1 -0
- package/dist/lib/shared/register-globals.d.ts +14 -0
- package/dist/lib/shared/register-globals.d.ts.map +1 -0
- package/dist/lib/shared/register-globals.js +21 -0
- package/dist/lib/shared/register-globals.js.map +1 -0
- package/dist/lib/{theme.d.ts → shared/theme.d.ts} +1 -18
- package/dist/lib/shared/theme.d.ts.map +1 -0
- package/dist/lib/{theme.js → shared/theme.js} +1 -18
- package/dist/lib/shared/theme.js.map +1 -0
- package/dist/lib/shared/types.d.ts.map +1 -0
- package/dist/lib/shared/types.js.map +1 -0
- package/dist/lib/ui/back-to-top.d.ts +13 -0
- package/dist/lib/ui/back-to-top.d.ts.map +1 -0
- package/dist/lib/ui/back-to-top.js +48 -0
- package/dist/lib/ui/back-to-top.js.map +1 -0
- package/dist/scripts/analysis-reader.d.ts +241 -0
- package/dist/scripts/analysis-reader.d.ts.map +1 -0
- package/dist/scripts/analysis-reader.js +495 -0
- package/dist/scripts/analysis-reader.js.map +1 -0
- package/dist/scripts/article-quality-enhancer.d.ts +148 -0
- package/dist/scripts/article-quality-enhancer.d.ts.map +1 -0
- package/dist/scripts/article-quality-enhancer.js +430 -0
- package/dist/scripts/article-quality-enhancer.js.map +1 -0
- package/dist/scripts/article-template/constants.d.ts +104 -0
- package/dist/scripts/article-template/constants.d.ts.map +1 -0
- package/dist/scripts/article-template/constants.js +225 -0
- package/dist/scripts/article-template/constants.js.map +1 -0
- package/dist/scripts/article-template/helpers.d.ts +83 -0
- package/dist/scripts/article-template/helpers.d.ts.map +1 -0
- package/dist/scripts/article-template/helpers.js +236 -0
- package/dist/scripts/article-template/helpers.js.map +1 -0
- package/dist/scripts/article-template/index.d.ts +39 -0
- package/dist/scripts/article-template/index.d.ts.map +1 -0
- package/dist/scripts/article-template/index.js +37 -0
- package/dist/scripts/article-template/index.js.map +1 -0
- package/dist/scripts/article-template/registry.d.ts +64 -0
- package/dist/scripts/article-template/registry.d.ts.map +1 -0
- package/dist/scripts/article-template/registry.js +405 -0
- package/dist/scripts/article-template/registry.js.map +1 -0
- package/dist/scripts/article-template/template.d.ts +18 -0
- package/dist/scripts/article-template/template.d.ts.map +1 -0
- package/dist/scripts/article-template/template.js +359 -0
- package/dist/scripts/article-template/template.js.map +1 -0
- package/dist/scripts/article-template/types.d.ts +90 -0
- package/dist/scripts/article-template/types.d.ts.map +1 -0
- package/dist/scripts/article-template/types.js +96 -0
- package/dist/scripts/article-template/types.js.map +1 -0
- package/dist/scripts/article-template.d.ts +18 -0
- package/dist/scripts/article-template.d.ts.map +1 -0
- package/dist/scripts/article-template.js +18 -0
- package/dist/scripts/article-template.js.map +1 -0
- package/dist/scripts/catalog-downloaded-data.d.ts +66 -0
- package/dist/scripts/catalog-downloaded-data.d.ts.map +1 -0
- package/dist/scripts/catalog-downloaded-data.js +239 -0
- package/dist/scripts/catalog-downloaded-data.js.map +1 -0
- package/dist/scripts/check-cia-schema-updates.d.ts +186 -0
- package/dist/scripts/check-cia-schema-updates.d.ts.map +1 -0
- package/dist/scripts/check-cia-schema-updates.js +363 -0
- package/dist/scripts/check-cia-schema-updates.js.map +1 -0
- package/dist/scripts/data-transformers/calendar.d.ts +16 -0
- package/dist/scripts/data-transformers/calendar.d.ts.map +1 -0
- package/dist/scripts/data-transformers/calendar.js +137 -0
- package/dist/scripts/data-transformers/calendar.js.map +1 -0
- package/dist/scripts/data-transformers/constants/committee-names.d.ts +15 -0
- package/dist/scripts/data-transformers/constants/committee-names.d.ts.map +1 -0
- package/dist/scripts/data-transformers/constants/committee-names.js +30 -0
- package/dist/scripts/data-transformers/constants/committee-names.js.map +1 -0
- package/dist/scripts/data-transformers/constants/content-labels-part1.d.ts +11 -0
- package/dist/scripts/data-transformers/constants/content-labels-part1.d.ts.map +1 -0
- package/dist/scripts/data-transformers/constants/content-labels-part1.js +900 -0
- package/dist/scripts/data-transformers/constants/content-labels-part1.js.map +1 -0
- package/dist/scripts/data-transformers/constants/content-labels-part2.d.ts +11 -0
- package/dist/scripts/data-transformers/constants/content-labels-part2.d.ts.map +1 -0
- package/dist/scripts/data-transformers/constants/content-labels-part2.js +900 -0
- package/dist/scripts/data-transformers/constants/content-labels-part2.js.map +1 -0
- package/dist/scripts/data-transformers/constants/content-labels.d.ts +13 -0
- package/dist/scripts/data-transformers/constants/content-labels.d.ts.map +1 -0
- package/dist/scripts/data-transformers/constants/content-labels.js +18 -0
- package/dist/scripts/data-transformers/constants/content-labels.js.map +1 -0
- package/dist/scripts/data-transformers/constants/index.d.ts +15 -0
- package/dist/scripts/data-transformers/constants/index.d.ts.map +1 -0
- package/dist/scripts/data-transformers/constants/index.js +15 -0
- package/dist/scripts/data-transformers/constants/index.js.map +1 -0
- package/dist/scripts/data-transformers/constants/locale-map.d.ts +13 -0
- package/dist/scripts/data-transformers/constants/locale-map.d.ts.map +1 -0
- package/dist/scripts/data-transformers/constants/locale-map.js +17 -0
- package/dist/scripts/data-transformers/constants/locale-map.js.map +1 -0
- package/dist/scripts/data-transformers/constants.d.ts +14 -0
- package/dist/scripts/data-transformers/constants.d.ts.map +1 -0
- package/dist/scripts/data-transformers/constants.js +14 -0
- package/dist/scripts/data-transformers/constants.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/ai-mindmap-analyzer.d.ts +64 -0
- package/dist/scripts/data-transformers/content-generators/ai-mindmap-analyzer.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/ai-mindmap-analyzer.js +805 -0
- package/dist/scripts/data-transformers/content-generators/ai-mindmap-analyzer.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/cia-overview-section.d.ts +50 -0
- package/dist/scripts/data-transformers/content-generators/cia-overview-section.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/cia-overview-section.js +310 -0
- package/dist/scripts/data-transformers/content-generators/cia-overview-section.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/committee.d.ts +12 -0
- package/dist/scripts/data-transformers/content-generators/committee.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/committee.js +247 -0
- package/dist/scripts/data-transformers/content-generators/committee.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/dashboard-section.d.ts +124 -0
- package/dist/scripts/data-transformers/content-generators/dashboard-section.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/dashboard-section.js +564 -0
- package/dist/scripts/data-transformers/content-generators/dashboard-section.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/economic-dashboard-section.d.ts +82 -0
- package/dist/scripts/data-transformers/content-generators/economic-dashboard-section.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/economic-dashboard-section.js +321 -0
- package/dist/scripts/data-transformers/content-generators/economic-dashboard-section.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/generic.d.ts +12 -0
- package/dist/scripts/data-transformers/content-generators/generic.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/generic.js +295 -0
- package/dist/scripts/data-transformers/content-generators/generic.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/index.d.ts +84 -0
- package/dist/scripts/data-transformers/content-generators/index.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/index.js +47 -0
- package/dist/scripts/data-transformers/content-generators/index.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/interpellations.d.ts +12 -0
- package/dist/scripts/data-transformers/content-generators/interpellations.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/interpellations.js +124 -0
- package/dist/scripts/data-transformers/content-generators/interpellations.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/mindmap-section.d.ts +137 -0
- package/dist/scripts/data-transformers/content-generators/mindmap-section.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/mindmap-section.js +286 -0
- package/dist/scripts/data-transformers/content-generators/mindmap-section.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/month-ahead.d.ts +17 -0
- package/dist/scripts/data-transformers/content-generators/month-ahead.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/month-ahead.js +212 -0
- package/dist/scripts/data-transformers/content-generators/month-ahead.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/monthly-review.d.ts +17 -0
- package/dist/scripts/data-transformers/content-generators/monthly-review.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/monthly-review.js +173 -0
- package/dist/scripts/data-transformers/content-generators/monthly-review.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/motions.d.ts +12 -0
- package/dist/scripts/data-transformers/content-generators/motions.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/motions.js +158 -0
- package/dist/scripts/data-transformers/content-generators/motions.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/newsworthiness.d.ts +52 -0
- package/dist/scripts/data-transformers/content-generators/newsworthiness.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/newsworthiness.js +244 -0
- package/dist/scripts/data-transformers/content-generators/newsworthiness.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/propositions.d.ts +12 -0
- package/dist/scripts/data-transformers/content-generators/propositions.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/propositions.js +263 -0
- package/dist/scripts/data-transformers/content-generators/propositions.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/sankey-section.d.ts +92 -0
- package/dist/scripts/data-transformers/content-generators/sankey-section.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/sankey-section.js +257 -0
- package/dist/scripts/data-transformers/content-generators/sankey-section.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/shared.d.ts +123 -0
- package/dist/scripts/data-transformers/content-generators/shared.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/shared.js +919 -0
- package/dist/scripts/data-transformers/content-generators/shared.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/swot-section.d.ts +50 -0
- package/dist/scripts/data-transformers/content-generators/swot-section.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/swot-section.js +177 -0
- package/dist/scripts/data-transformers/content-generators/swot-section.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators/week-ahead.d.ts +12 -0
- package/dist/scripts/data-transformers/content-generators/week-ahead.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators/week-ahead.js +332 -0
- package/dist/scripts/data-transformers/content-generators/week-ahead.js.map +1 -0
- package/dist/scripts/data-transformers/content-generators.d.ts +15 -0
- package/dist/scripts/data-transformers/content-generators.d.ts.map +1 -0
- package/dist/scripts/data-transformers/content-generators.js +14 -0
- package/dist/scripts/data-transformers/content-generators.js.map +1 -0
- package/dist/scripts/data-transformers/document-analysis.d.ts +58 -0
- package/dist/scripts/data-transformers/document-analysis.d.ts.map +1 -0
- package/dist/scripts/data-transformers/document-analysis.js +396 -0
- package/dist/scripts/data-transformers/document-analysis.js.map +1 -0
- package/dist/scripts/data-transformers/helpers.d.ts +144 -0
- package/dist/scripts/data-transformers/helpers.d.ts.map +1 -0
- package/dist/scripts/data-transformers/helpers.js +408 -0
- package/dist/scripts/data-transformers/helpers.js.map +1 -0
- package/dist/scripts/data-transformers/index.d.ts +45 -0
- package/dist/scripts/data-transformers/index.d.ts.map +1 -0
- package/dist/scripts/data-transformers/index.js +66 -0
- package/dist/scripts/data-transformers/index.js.map +1 -0
- package/dist/scripts/data-transformers/metadata.d.ts +42 -0
- package/dist/scripts/data-transformers/metadata.d.ts.map +1 -0
- package/dist/scripts/data-transformers/metadata.js +385 -0
- package/dist/scripts/data-transformers/metadata.js.map +1 -0
- package/dist/scripts/data-transformers/policy-analysis.d.ts +105 -0
- package/dist/scripts/data-transformers/policy-analysis.d.ts.map +1 -0
- package/dist/scripts/data-transformers/policy-analysis.js +686 -0
- package/dist/scripts/data-transformers/policy-analysis.js.map +1 -0
- package/dist/scripts/data-transformers/risk-analysis.d.ts +104 -0
- package/dist/scripts/data-transformers/risk-analysis.d.ts.map +1 -0
- package/dist/scripts/data-transformers/risk-analysis.js +279 -0
- package/dist/scripts/data-transformers/risk-analysis.js.map +1 -0
- package/dist/scripts/data-transformers/types.d.ts +260 -0
- package/dist/scripts/data-transformers/types.d.ts.map +1 -0
- package/dist/scripts/data-transformers/types.js +11 -0
- package/dist/scripts/data-transformers/types.js.map +1 -0
- package/dist/scripts/data-transformers.d.ts +23 -0
- package/dist/scripts/data-transformers.d.ts.map +1 -0
- package/dist/scripts/data-transformers.js +35 -0
- package/dist/scripts/data-transformers.js.map +1 -0
- package/dist/scripts/deep-inspection/index.d.ts +55 -0
- package/dist/scripts/deep-inspection/index.d.ts.map +1 -0
- package/dist/scripts/deep-inspection/index.js +66 -0
- package/dist/scripts/deep-inspection/index.js.map +1 -0
- package/dist/scripts/detect-swedish-leakage.d.ts +69 -0
- package/dist/scripts/detect-swedish-leakage.d.ts.map +1 -0
- package/dist/scripts/detect-swedish-leakage.js +417 -0
- package/dist/scripts/detect-swedish-leakage.js.map +1 -0
- package/dist/scripts/editorial-framework.d.ts +121 -0
- package/dist/scripts/editorial-framework.d.ts.map +1 -0
- package/dist/scripts/editorial-framework.js +364 -0
- package/dist/scripts/editorial-framework.js.map +1 -0
- package/dist/scripts/editorial-pillars.d.ts +43 -0
- package/dist/scripts/editorial-pillars.d.ts.map +1 -0
- package/dist/scripts/editorial-pillars.js +215 -0
- package/dist/scripts/editorial-pillars.js.map +1 -0
- package/dist/scripts/extract-news-metadata.d.ts +12 -0
- package/dist/scripts/extract-news-metadata.d.ts.map +1 -0
- package/dist/scripts/extract-news-metadata.js +107 -0
- package/dist/scripts/extract-news-metadata.js.map +1 -0
- package/dist/scripts/extract-vocabulary.d.ts +15 -0
- package/dist/scripts/extract-vocabulary.d.ts.map +1 -0
- package/dist/scripts/extract-vocabulary.js +255 -0
- package/dist/scripts/extract-vocabulary.js.map +1 -0
- package/dist/scripts/fix-article-navigation.d.ts +37 -0
- package/dist/scripts/fix-article-navigation.d.ts.map +1 -0
- package/dist/scripts/fix-article-navigation.js +198 -0
- package/dist/scripts/fix-article-navigation.js.map +1 -0
- package/dist/scripts/fix-keywords-localization.d.ts +18 -0
- package/dist/scripts/fix-keywords-localization.d.ts.map +1 -0
- package/dist/scripts/fix-keywords-localization.js +270 -0
- package/dist/scripts/fix-keywords-localization.js.map +1 -0
- package/dist/scripts/fix-old-articles-branding.d.ts +17 -0
- package/dist/scripts/fix-old-articles-branding.d.ts.map +1 -0
- package/dist/scripts/fix-old-articles-branding.js +229 -0
- package/dist/scripts/fix-old-articles-branding.js.map +1 -0
- package/dist/scripts/generate-news-backport.d.ts +16 -0
- package/dist/scripts/generate-news-backport.d.ts.map +1 -0
- package/dist/scripts/generate-news-backport.js +379 -0
- package/dist/scripts/generate-news-backport.js.map +1 -0
- package/dist/scripts/generate-news-enhanced/ai-analysis-pipeline.d.ts +124 -0
- package/dist/scripts/generate-news-enhanced/ai-analysis-pipeline.d.ts.map +1 -0
- package/dist/scripts/generate-news-enhanced/ai-analysis-pipeline.js +529 -0
- package/dist/scripts/generate-news-enhanced/ai-analysis-pipeline.js.map +1 -0
- package/dist/scripts/generate-news-enhanced/analysis-cache.d.ts +59 -0
- package/dist/scripts/generate-news-enhanced/analysis-cache.d.ts.map +1 -0
- package/dist/scripts/generate-news-enhanced/analysis-cache.js +116 -0
- package/dist/scripts/generate-news-enhanced/analysis-cache.js.map +1 -0
- package/dist/scripts/generate-news-enhanced/analysis-labels.d.ts +58 -0
- package/dist/scripts/generate-news-enhanced/analysis-labels.d.ts.map +1 -0
- package/dist/scripts/generate-news-enhanced/analysis-labels.js +144 -0
- package/dist/scripts/generate-news-enhanced/analysis-labels.js.map +1 -0
- package/dist/scripts/generate-news-enhanced/config.d.ts +58 -0
- package/dist/scripts/generate-news-enhanced/config.d.ts.map +1 -0
- package/dist/scripts/generate-news-enhanced/config.js +286 -0
- package/dist/scripts/generate-news-enhanced/config.js.map +1 -0
- package/dist/scripts/generate-news-enhanced/generators.d.ts +119 -0
- package/dist/scripts/generate-news-enhanced/generators.d.ts.map +1 -0
- package/dist/scripts/generate-news-enhanced/generators.js +2131 -0
- package/dist/scripts/generate-news-enhanced/generators.js.map +1 -0
- package/dist/scripts/generate-news-enhanced/helpers.d.ts +122 -0
- package/dist/scripts/generate-news-enhanced/helpers.d.ts.map +1 -0
- package/dist/scripts/generate-news-enhanced/helpers.js +468 -0
- package/dist/scripts/generate-news-enhanced/helpers.js.map +1 -0
- package/dist/scripts/generate-news-enhanced/index.d.ts +19 -0
- package/dist/scripts/generate-news-enhanced/index.d.ts.map +1 -0
- package/dist/scripts/generate-news-enhanced/index.js +221 -0
- package/dist/scripts/generate-news-enhanced/index.js.map +1 -0
- package/dist/scripts/generate-news-enhanced/swot-analyzer.d.ts +46 -0
- package/dist/scripts/generate-news-enhanced/swot-analyzer.d.ts.map +1 -0
- package/dist/scripts/generate-news-enhanced/swot-analyzer.js +1227 -0
- package/dist/scripts/generate-news-enhanced/swot-analyzer.js.map +1 -0
- package/dist/scripts/generate-news-enhanced/types.d.ts +34 -0
- package/dist/scripts/generate-news-enhanced/types.d.ts.map +1 -0
- package/dist/scripts/generate-news-enhanced/types.js +9 -0
- package/dist/scripts/generate-news-enhanced/types.js.map +1 -0
- package/dist/scripts/generate-news-enhanced.d.ts +25 -0
- package/dist/scripts/generate-news-enhanced.d.ts.map +1 -0
- package/dist/scripts/generate-news-enhanced.js +37 -0
- package/dist/scripts/generate-news-enhanced.js.map +1 -0
- package/dist/scripts/generate-news-indexes/constants.d.ts +20 -0
- package/dist/scripts/generate-news-indexes/constants.d.ts.map +1 -0
- package/dist/scripts/generate-news-indexes/constants.js +308 -0
- package/dist/scripts/generate-news-indexes/constants.js.map +1 -0
- package/dist/scripts/generate-news-indexes/helpers.d.ts +73 -0
- package/dist/scripts/generate-news-indexes/helpers.d.ts.map +1 -0
- package/dist/scripts/generate-news-indexes/helpers.js +352 -0
- package/dist/scripts/generate-news-indexes/helpers.js.map +1 -0
- package/dist/scripts/generate-news-indexes/index.d.ts +20 -0
- package/dist/scripts/generate-news-indexes/index.d.ts.map +1 -0
- package/dist/scripts/generate-news-indexes/index.js +75 -0
- package/dist/scripts/generate-news-indexes/index.js.map +1 -0
- package/dist/scripts/generate-news-indexes/template.d.ts +25 -0
- package/dist/scripts/generate-news-indexes/template.d.ts.map +1 -0
- package/dist/scripts/generate-news-indexes/template.js +644 -0
- package/dist/scripts/generate-news-indexes/template.js.map +1 -0
- package/dist/scripts/generate-news-indexes/types.d.ts +96 -0
- package/dist/scripts/generate-news-indexes/types.d.ts.map +1 -0
- package/dist/scripts/generate-news-indexes/types.js +9 -0
- package/dist/scripts/generate-news-indexes/types.js.map +1 -0
- package/dist/scripts/generate-news-indexes.d.ts +20 -0
- package/dist/scripts/generate-news-indexes.d.ts.map +1 -0
- package/dist/scripts/generate-news-indexes.js +19 -0
- package/dist/scripts/generate-news-indexes.js.map +1 -0
- package/dist/scripts/generate-rss.d.ts +48 -0
- package/dist/scripts/generate-rss.d.ts.map +1 -0
- package/dist/scripts/generate-rss.js +299 -0
- package/dist/scripts/generate-rss.js.map +1 -0
- package/dist/scripts/generate-sitemap-html.d.ts +79 -0
- package/dist/scripts/generate-sitemap-html.d.ts.map +1 -0
- package/dist/scripts/generate-sitemap-html.js +878 -0
- package/dist/scripts/generate-sitemap-html.js.map +1 -0
- package/dist/scripts/generate-sitemap.d.ts +23 -0
- package/dist/scripts/generate-sitemap.d.ts.map +1 -0
- package/dist/scripts/generate-sitemap.js +476 -0
- package/dist/scripts/generate-sitemap.js.map +1 -0
- package/dist/scripts/generate-types-from-cia-schemas.d.ts +176 -0
- package/dist/scripts/generate-types-from-cia-schemas.d.ts.map +1 -0
- package/dist/scripts/generate-types-from-cia-schemas.js +318 -0
- package/dist/scripts/generate-types-from-cia-schemas.js.map +1 -0
- package/dist/scripts/government-role-validator.d.ts +65 -0
- package/dist/scripts/government-role-validator.d.ts.map +1 -0
- package/dist/scripts/government-role-validator.js +217 -0
- package/dist/scripts/government-role-validator.js.map +1 -0
- package/dist/scripts/html-utils.d.ts +17 -0
- package/dist/scripts/html-utils.d.ts.map +1 -0
- package/dist/scripts/html-utils.js +29 -0
- package/dist/scripts/html-utils.js.map +1 -0
- package/dist/scripts/load-cia-stats.d.ts +89 -0
- package/dist/scripts/load-cia-stats.d.ts.map +1 -0
- package/dist/scripts/load-cia-stats.js +400 -0
- package/dist/scripts/load-cia-stats.js.map +1 -0
- package/dist/scripts/mcp-client/client.d.ts +73 -0
- package/dist/scripts/mcp-client/client.d.ts.map +1 -0
- package/dist/scripts/mcp-client/client.js +526 -0
- package/dist/scripts/mcp-client/client.js.map +1 -0
- package/dist/scripts/mcp-client/document-types.d.ts +23 -0
- package/dist/scripts/mcp-client/document-types.d.ts.map +1 -0
- package/dist/scripts/mcp-client/document-types.js +62 -0
- package/dist/scripts/mcp-client/document-types.js.map +1 -0
- package/dist/scripts/mcp-client/index.d.ts +34 -0
- package/dist/scripts/mcp-client/index.d.ts.map +1 -0
- package/dist/scripts/mcp-client/index.js +67 -0
- package/dist/scripts/mcp-client/index.js.map +1 -0
- package/dist/scripts/mcp-client/transport.d.ts +32 -0
- package/dist/scripts/mcp-client/transport.d.ts.map +1 -0
- package/dist/scripts/mcp-client/transport.js +102 -0
- package/dist/scripts/mcp-client/transport.js.map +1 -0
- package/dist/scripts/mcp-client.d.ts +18 -0
- package/dist/scripts/mcp-client.d.ts.map +1 -0
- package/dist/scripts/mcp-client.js +18 -0
- package/dist/scripts/mcp-client.js.map +1 -0
- package/dist/scripts/mcp-query-cli.d.ts +25 -0
- package/dist/scripts/mcp-query-cli.d.ts.map +1 -0
- package/dist/scripts/mcp-query-cli.js +66 -0
- package/dist/scripts/mcp-query-cli.js.map +1 -0
- package/dist/scripts/news-types/breaking-news.d.ts +178 -0
- package/dist/scripts/news-types/breaking-news.d.ts.map +1 -0
- package/dist/scripts/news-types/breaking-news.js +432 -0
- package/dist/scripts/news-types/breaking-news.js.map +1 -0
- package/dist/scripts/news-types/committee-reports.d.ts +212 -0
- package/dist/scripts/news-types/committee-reports.d.ts.map +1 -0
- package/dist/scripts/news-types/committee-reports.js +404 -0
- package/dist/scripts/news-types/committee-reports.js.map +1 -0
- package/dist/scripts/news-types/month-ahead.d.ts +71 -0
- package/dist/scripts/news-types/month-ahead.d.ts.map +1 -0
- package/dist/scripts/news-types/month-ahead.js +392 -0
- package/dist/scripts/news-types/month-ahead.js.map +1 -0
- package/dist/scripts/news-types/monthly-review.d.ts +61 -0
- package/dist/scripts/news-types/monthly-review.d.ts.map +1 -0
- package/dist/scripts/news-types/monthly-review.js +417 -0
- package/dist/scripts/news-types/monthly-review.js.map +1 -0
- package/dist/scripts/news-types/motions.d.ts +214 -0
- package/dist/scripts/news-types/motions.d.ts.map +1 -0
- package/dist/scripts/news-types/motions.js +447 -0
- package/dist/scripts/news-types/motions.js.map +1 -0
- package/dist/scripts/news-types/propositions.d.ts +204 -0
- package/dist/scripts/news-types/propositions.d.ts.map +1 -0
- package/dist/scripts/news-types/propositions.js +401 -0
- package/dist/scripts/news-types/propositions.js.map +1 -0
- package/dist/scripts/news-types/week-ahead.d.ts +231 -0
- package/dist/scripts/news-types/week-ahead.d.ts.map +1 -0
- package/dist/scripts/news-types/week-ahead.js +441 -0
- package/dist/scripts/news-types/week-ahead.js.map +1 -0
- package/dist/scripts/news-types/weekly-review/analysis.d.ts +47 -0
- package/dist/scripts/news-types/weekly-review/analysis.d.ts.map +1 -0
- package/dist/scripts/news-types/weekly-review/analysis.js +276 -0
- package/dist/scripts/news-types/weekly-review/analysis.js.map +1 -0
- package/dist/scripts/news-types/weekly-review/data-loader.d.ts +51 -0
- package/dist/scripts/news-types/weekly-review/data-loader.d.ts.map +1 -0
- package/dist/scripts/news-types/weekly-review/data-loader.js +298 -0
- package/dist/scripts/news-types/weekly-review/data-loader.js.map +1 -0
- package/dist/scripts/news-types/weekly-review/generator.d.ts +18 -0
- package/dist/scripts/news-types/weekly-review/generator.d.ts.map +1 -0
- package/dist/scripts/news-types/weekly-review/generator.js +284 -0
- package/dist/scripts/news-types/weekly-review/generator.js.map +1 -0
- package/dist/scripts/news-types/weekly-review/index.d.ts +20 -0
- package/dist/scripts/news-types/weekly-review/index.d.ts.map +1 -0
- package/dist/scripts/news-types/weekly-review/index.js +23 -0
- package/dist/scripts/news-types/weekly-review/index.js.map +1 -0
- package/dist/scripts/news-types/weekly-review/types.d.ts +72 -0
- package/dist/scripts/news-types/weekly-review/types.d.ts.map +1 -0
- package/dist/scripts/news-types/weekly-review/types.js +20 -0
- package/dist/scripts/news-types/weekly-review/types.js.map +1 -0
- package/dist/scripts/news-types/weekly-review/validation.d.ts +10 -0
- package/dist/scripts/news-types/weekly-review/validation.d.ts.map +1 -0
- package/dist/scripts/news-types/weekly-review/validation.js +95 -0
- package/dist/scripts/news-types/weekly-review/validation.js.map +1 -0
- package/dist/scripts/news-types/weekly-review.d.ts +15 -0
- package/dist/scripts/news-types/weekly-review.d.ts.map +1 -0
- package/dist/scripts/news-types/weekly-review.js +14 -0
- package/dist/scripts/news-types/weekly-review.js.map +1 -0
- package/dist/scripts/party-variants.d.ts +23 -0
- package/dist/scripts/party-variants.d.ts.map +1 -0
- package/dist/scripts/party-variants.js +50 -0
- package/dist/scripts/party-variants.js.map +1 -0
- package/dist/scripts/pipeline/index.d.ts +19 -0
- package/dist/scripts/pipeline/index.d.ts.map +1 -0
- package/dist/scripts/pipeline/index.js +17 -0
- package/dist/scripts/pipeline/index.js.map +1 -0
- package/dist/scripts/pipeline/orchestrator.d.ts +56 -0
- package/dist/scripts/pipeline/orchestrator.d.ts.map +1 -0
- package/dist/scripts/pipeline/orchestrator.js +147 -0
- package/dist/scripts/pipeline/orchestrator.js.map +1 -0
- package/dist/scripts/pipeline/types.d.ts +117 -0
- package/dist/scripts/pipeline/types.d.ts.map +1 -0
- package/dist/scripts/pipeline/types.js +13 -0
- package/dist/scripts/pipeline/types.js.map +1 -0
- package/dist/scripts/pipeline/validation.d.ts +66 -0
- package/dist/scripts/pipeline/validation.d.ts.map +1 -0
- package/dist/scripts/pipeline/validation.js +129 -0
- package/dist/scripts/pipeline/validation.js.map +1 -0
- package/dist/scripts/populate-analysis-data.d.ts +41 -0
- package/dist/scripts/populate-analysis-data.d.ts.map +1 -0
- package/dist/scripts/populate-analysis-data.js +255 -0
- package/dist/scripts/populate-analysis-data.js.map +1 -0
- package/dist/scripts/pre-article-analysis/data-downloader.d.ts +80 -0
- package/dist/scripts/pre-article-analysis/data-downloader.d.ts.map +1 -0
- package/dist/scripts/pre-article-analysis/data-downloader.js +230 -0
- package/dist/scripts/pre-article-analysis/data-downloader.js.map +1 -0
- package/dist/scripts/pre-article-analysis/data-persistence.d.ts +141 -0
- package/dist/scripts/pre-article-analysis/data-persistence.d.ts.map +1 -0
- package/dist/scripts/pre-article-analysis/data-persistence.js +345 -0
- package/dist/scripts/pre-article-analysis/data-persistence.js.map +1 -0
- package/dist/scripts/pre-article-analysis/markdown-serializer.d.ts +158 -0
- package/dist/scripts/pre-article-analysis/markdown-serializer.d.ts.map +1 -0
- package/dist/scripts/pre-article-analysis/markdown-serializer.js +648 -0
- package/dist/scripts/pre-article-analysis/markdown-serializer.js.map +1 -0
- package/dist/scripts/pre-article-analysis/pdf-converter.d.ts +58 -0
- package/dist/scripts/pre-article-analysis/pdf-converter.d.ts.map +1 -0
- package/dist/scripts/pre-article-analysis/pdf-converter.js +142 -0
- package/dist/scripts/pre-article-analysis/pdf-converter.js.map +1 -0
- package/dist/scripts/pre-article-analysis.d.ts +41 -0
- package/dist/scripts/pre-article-analysis.d.ts.map +1 -0
- package/dist/scripts/pre-article-analysis.js +591 -0
- package/dist/scripts/pre-article-analysis.js.map +1 -0
- package/dist/scripts/scb-client.d.ts +104 -0
- package/dist/scripts/scb-client.d.ts.map +1 -0
- package/dist/scripts/scb-client.js +286 -0
- package/dist/scripts/scb-client.js.map +1 -0
- package/dist/scripts/scb-context.d.ts +88 -0
- package/dist/scripts/scb-context.d.ts.map +1 -0
- package/dist/scripts/scb-context.js +307 -0
- package/dist/scripts/scb-context.js.map +1 -0
- package/dist/scripts/shared/version.d.ts +9 -0
- package/dist/scripts/shared/version.d.ts.map +1 -0
- package/dist/scripts/shared/version.js +28 -0
- package/dist/scripts/shared/version.js.map +1 -0
- package/dist/scripts/statistical-claims-detector.d.ts +119 -0
- package/dist/scripts/statistical-claims-detector.d.ts.map +1 -0
- package/dist/scripts/statistical-claims-detector.js +391 -0
- package/dist/scripts/statistical-claims-detector.js.map +1 -0
- package/dist/scripts/sync-cia-schemas.d.ts +52 -0
- package/dist/scripts/sync-cia-schemas.d.ts.map +1 -0
- package/dist/scripts/sync-cia-schemas.js +195 -0
- package/dist/scripts/sync-cia-schemas.js.map +1 -0
- package/dist/scripts/translation-dictionary.d.ts +45 -0
- package/dist/scripts/translation-dictionary.d.ts.map +1 -0
- package/dist/scripts/translation-dictionary.js +3642 -0
- package/dist/scripts/translation-dictionary.js.map +1 -0
- package/dist/scripts/types/article.d.ts +392 -0
- package/dist/scripts/types/article.d.ts.map +1 -0
- package/dist/scripts/types/article.js +6 -0
- package/dist/scripts/types/article.js.map +1 -0
- package/dist/scripts/types/content.d.ts +167 -0
- package/dist/scripts/types/content.d.ts.map +1 -0
- package/dist/scripts/types/content.js +6 -0
- package/dist/scripts/types/content.js.map +1 -0
- package/dist/scripts/types/editorial.d.ts +17 -0
- package/dist/scripts/types/editorial.d.ts.map +1 -0
- package/dist/scripts/types/editorial.js +6 -0
- package/dist/scripts/types/editorial.js.map +1 -0
- package/dist/scripts/types/language.d.ts +7 -0
- package/dist/scripts/types/language.d.ts.map +1 -0
- package/dist/scripts/types/language.js +6 -0
- package/dist/scripts/types/language.js.map +1 -0
- package/dist/scripts/types/mcp.d.ts +117 -0
- package/dist/scripts/types/mcp.d.ts.map +1 -0
- package/dist/scripts/types/mcp.js +6 -0
- package/dist/scripts/types/mcp.js.map +1 -0
- package/dist/scripts/types/party.d.ts +9 -0
- package/dist/scripts/types/party.d.ts.map +1 -0
- package/dist/scripts/types/party.js +6 -0
- package/dist/scripts/types/party.js.map +1 -0
- package/dist/scripts/types/validation.d.ts +136 -0
- package/dist/scripts/types/validation.d.ts.map +1 -0
- package/dist/scripts/types/validation.js +6 -0
- package/dist/scripts/types/validation.js.map +1 -0
- package/dist/scripts/types/workflow.d.ts +78 -0
- package/dist/scripts/types/workflow.d.ts.map +1 -0
- package/dist/scripts/types/workflow.js +6 -0
- package/dist/scripts/types/workflow.js.map +1 -0
- package/dist/scripts/update-stats-from-cia.d.ts +44 -0
- package/dist/scripts/update-stats-from-cia.d.ts.map +1 -0
- package/dist/scripts/update-stats-from-cia.js +310 -0
- package/dist/scripts/update-stats-from-cia.js.map +1 -0
- package/dist/scripts/validate-against-cia-schemas.d.ts +126 -0
- package/dist/scripts/validate-against-cia-schemas.d.ts.map +1 -0
- package/dist/scripts/validate-against-cia-schemas.js +299 -0
- package/dist/scripts/validate-against-cia-schemas.js.map +1 -0
- package/dist/scripts/validate-cross-references.d.ts +49 -0
- package/dist/scripts/validate-cross-references.d.ts.map +1 -0
- package/dist/scripts/validate-cross-references.js +183 -0
- package/dist/scripts/validate-cross-references.js.map +1 -0
- package/dist/scripts/validate-file-ownership.d.ts +68 -0
- package/dist/scripts/validate-file-ownership.d.ts.map +1 -0
- package/dist/scripts/validate-file-ownership.js +135 -0
- package/dist/scripts/validate-file-ownership.js.map +1 -0
- package/dist/scripts/validate-news-translations.d.ts +27 -0
- package/dist/scripts/validate-news-translations.d.ts.map +1 -0
- package/dist/scripts/validate-news-translations.js +258 -0
- package/dist/scripts/validate-news-translations.js.map +1 -0
- package/dist/scripts/validate-translations.d.ts +162 -0
- package/dist/scripts/validate-translations.d.ts.map +1 -0
- package/dist/scripts/validate-translations.js +378 -0
- package/dist/scripts/validate-translations.js.map +1 -0
- package/dist/scripts/workflow-state-coordinator.d.ts +354 -0
- package/dist/scripts/workflow-state-coordinator.d.ts.map +1 -0
- package/dist/scripts/workflow-state-coordinator.js +876 -0
- package/dist/scripts/workflow-state-coordinator.js.map +1 -0
- package/dist/scripts/world-bank-client.d.ts +122 -0
- package/dist/scripts/world-bank-client.d.ts.map +1 -0
- package/dist/scripts/world-bank-client.js +192 -0
- package/dist/scripts/world-bank-client.js.map +1 -0
- package/dist/scripts/world-bank-context.d.ts +88 -0
- package/dist/scripts/world-bank-context.d.ts.map +1 -0
- package/dist/scripts/world-bank-context.js +331 -0
- package/dist/scripts/world-bank-context.js.map +1 -0
- package/package.json +33 -8
- package/dist/lib/chart-factory.d.ts.map +0 -1
- package/dist/lib/chart-factory.js.map +0 -1
- package/dist/lib/data-loader.d.ts.map +0 -1
- package/dist/lib/data-loader.js.map +0 -1
- package/dist/lib/dom-utils.d.ts.map +0 -1
- package/dist/lib/dom-utils.js.map +0 -1
- package/dist/lib/error-boundary.d.ts.map +0 -1
- package/dist/lib/error-boundary.js.map +0 -1
- package/dist/lib/fallback-ui.d.ts.map +0 -1
- package/dist/lib/fallback-ui.js.map +0 -1
- package/dist/lib/index.d.ts.map +0 -1
- package/dist/lib/index.js.map +0 -1
- package/dist/lib/logger.d.ts.map +0 -1
- package/dist/lib/logger.js.map +0 -1
- package/dist/lib/theme.d.ts.map +0 -1
- package/dist/lib/theme.js.map +0 -1
- package/dist/lib/types.d.ts.map +0 -1
- package/dist/lib/types.js.map +0 -1
- /package/dist/lib/{data-loader.d.ts → shared/data-loader.d.ts} +0 -0
- /package/dist/lib/{data-loader.js → shared/data-loader.js} +0 -0
- /package/dist/lib/{dom-utils.d.ts → shared/dom-utils.d.ts} +0 -0
- /package/dist/lib/{dom-utils.js → shared/dom-utils.js} +0 -0
- /package/dist/lib/{error-boundary.d.ts → shared/error-boundary.d.ts} +0 -0
- /package/dist/lib/{error-boundary.js → shared/error-boundary.js} +0 -0
- /package/dist/lib/{fallback-ui.d.ts → shared/fallback-ui.d.ts} +0 -0
- /package/dist/lib/{fallback-ui.js → shared/fallback-ui.js} +0 -0
- /package/dist/lib/{index.d.ts → shared/index.d.ts} +0 -0
- /package/dist/lib/{index.js → shared/index.js} +0 -0
- /package/dist/lib/{logger.d.ts → shared/logger.d.ts} +0 -0
- /package/dist/lib/{logger.js → shared/logger.js} +0 -0
- /package/dist/lib/{types.d.ts → shared/types.d.ts} +0 -0
- /package/dist/lib/{types.js → shared/types.js} +0 -0
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @module Infrastructure/WorkflowOrchestration
|
|
4
|
+
* @category Infrastructure
|
|
5
|
+
*
|
|
6
|
+
* @title Workflow State Coordinator - Multi-Workflow Synchronization Engine
|
|
7
|
+
*
|
|
8
|
+
* @description
|
|
9
|
+
* **INTELLIGENCE OPERATIVE PERSPECTIVE**
|
|
10
|
+
*
|
|
11
|
+
* This module orchestrates coordination between three independent news generation
|
|
12
|
+
* workflows operating on different schedules, preventing wasted computational
|
|
13
|
+
* resources on duplicate article generation and maintaining editorial consistency.
|
|
14
|
+
* In intelligence operations, workflow state management prevents information
|
|
15
|
+
* redundancy and ensures efficient use of computational and editorial resources.
|
|
16
|
+
*
|
|
17
|
+
* **WORKFLOW ARCHITECTURE:**
|
|
18
|
+
* The platform operates three independent content generation workflows:
|
|
19
|
+
*
|
|
20
|
+
* 1. **Realtime Monitor (news-realtime-monitor.md)**
|
|
21
|
+
* Schedule: 2x daily (morning + afternoon)
|
|
22
|
+
* Content: Event-driven breaking news, voting updates, crisis response
|
|
23
|
+
* Intelligence value: Rapid notification of parliamentary surprises
|
|
24
|
+
* Latency: Real-time (5-15 minute response to events)
|
|
25
|
+
*
|
|
26
|
+
* 2. **Evening Analysis (news-evening-analysis.md)**
|
|
27
|
+
* Schedule: Daily at 17:00 (5 PM Swedish time)
|
|
28
|
+
* Content: Deep analytical synthesis, international context, forward assessment
|
|
29
|
+
* Intelligence value: End-of-day intelligence briefing format
|
|
30
|
+
* Latency: Structured analysis (1-2 hour research + writing)
|
|
31
|
+
*
|
|
32
|
+
* 3. **Article Generators (news-article-generator.md)**
|
|
33
|
+
* Schedule: Variable (triggered by content calendar or on-demand)
|
|
34
|
+
* Content: Committee reports, motions, propositions, week-ahead
|
|
35
|
+
* Intelligence value: Systematic coverage of all parliamentary products
|
|
36
|
+
* Latency: Scheduled batch processing (hourly to daily)
|
|
37
|
+
*
|
|
38
|
+
* **DEDUPLICATION FRAMEWORK:**
|
|
39
|
+
* The coordinator prevents duplicate article generation using similarity analysis:
|
|
40
|
+
*
|
|
41
|
+
* - **Similarity Threshold: 70%**
|
|
42
|
+
* Computes Levenshtein distance on article titles and keyword sets
|
|
43
|
+
* Articles >70% similar are considered duplicates
|
|
44
|
+
* Prevents wasted generation of already-covered topics
|
|
45
|
+
*
|
|
46
|
+
* - **Time-Window Filtering: 6 hours**
|
|
47
|
+
* Checks if similar article was generated in last 6 hours
|
|
48
|
+
* Allows coverage of same topic if sufficient time has passed
|
|
49
|
+
* Prevents rapid-fire duplicates while allowing topic revisits
|
|
50
|
+
*
|
|
51
|
+
* - **Topic-Based Tracking**
|
|
52
|
+
* Logs article topics (votes, bills, committees, etc.)
|
|
53
|
+
* Enables intelligent filtering at generation time
|
|
54
|
+
* Supports trending topic analysis
|
|
55
|
+
*
|
|
56
|
+
* **MCP QUERY CACHING:**
|
|
57
|
+
* To avoid redundant API calls to riksdag-regering MCP platform:
|
|
58
|
+
*
|
|
59
|
+
* - **Cache TTL: 2 hours**
|
|
60
|
+
* Stores results of expensive queries (voting patterns, full-text search)
|
|
61
|
+
* Reduces MCP server load during peak hours
|
|
62
|
+
* Ensures consistency across multiple workflow invocations
|
|
63
|
+
*
|
|
64
|
+
* - **Query Fingerprinting**
|
|
65
|
+
* Creates deterministic hash of MCP query parameters
|
|
66
|
+
* Enables cache hits even if queries structured differently
|
|
67
|
+
* Supports query normalization
|
|
68
|
+
*
|
|
69
|
+
* - **Staleness Handling**
|
|
70
|
+
* Fresh data (within 2 hours) used for analysis
|
|
71
|
+
* Older data triggers MCP refresh
|
|
72
|
+
* Prevents stale intelligence from being published
|
|
73
|
+
*
|
|
74
|
+
* **STATE MANAGEMENT:**
|
|
75
|
+
* Persistent state file (news/metadata/workflow-state.json) tracks:
|
|
76
|
+
* - Last workflow execution timestamp and results
|
|
77
|
+
* - Recently generated articles (content + timestamp)
|
|
78
|
+
* - MCP query cache with expiration times
|
|
79
|
+
* - Workflow coordination metadata
|
|
80
|
+
* - Running task list for cross-workflow visibility
|
|
81
|
+
*
|
|
82
|
+
* **OPERATIONAL WORKFLOW:**
|
|
83
|
+
* 1. Workflow begins: Load current state from persistent storage
|
|
84
|
+
* 2. Query Analysis: Check if similar article was recently generated
|
|
85
|
+
* 3. Cache Check: Retrieve cached MCP queries if available (<2hr old)
|
|
86
|
+
* 4. Generation: Create new article (or skip if duplicate)
|
|
87
|
+
* 5. State Update: Log article and update cache
|
|
88
|
+
* 6. Persistence: Write updated state for next workflow invocation
|
|
89
|
+
*
|
|
90
|
+
* **INCIDENT SCENARIOS:**
|
|
91
|
+
* - **Double-Generation**: Realtime Monitor and Article Generator both cover voting
|
|
92
|
+
* Solution: Similarity detection blocks duplicate, tracks in state
|
|
93
|
+
*
|
|
94
|
+
* - **Stale Analysis**: Evening Analysis uses MCP data from morning
|
|
95
|
+
* Solution: 2-hour cache expiration triggers fresh queries
|
|
96
|
+
*
|
|
97
|
+
* - **Missed Coverage**: Topic isn't covered by any workflow
|
|
98
|
+
* Solution: State logs enable gap analysis, manual workflow triggers
|
|
99
|
+
*
|
|
100
|
+
* - **Cache Corruption**: Stale query results cause analytical errors
|
|
101
|
+
* Solution: TTL-based expiration automatically refreshes
|
|
102
|
+
*
|
|
103
|
+
* **INTELLIGENCE APPLICATIONS:**
|
|
104
|
+
* - Prevents topic redundancy (editorial efficiency)
|
|
105
|
+
* - Ensures consistent coverage across workflows
|
|
106
|
+
* - Enables gap analysis (which topics are missed?)
|
|
107
|
+
* - Supports workflow optimization (timing, triggers)
|
|
108
|
+
* - Provides audit trail for editorial decisions
|
|
109
|
+
*
|
|
110
|
+
* **PERFORMANCE OPTIMIZATION:**
|
|
111
|
+
* - MCP cache reduces API calls by estimated 60-70%
|
|
112
|
+
* - Reduces computational load on MCP platform during peaks
|
|
113
|
+
* - Faster generation cycles (cache lookups faster than API calls)
|
|
114
|
+
* - Enables more frequent workflow execution
|
|
115
|
+
*
|
|
116
|
+
* **FAILURE MODES & RECOVERY:**
|
|
117
|
+
* - State file corruption: Graceful fallback to generation without deduplication
|
|
118
|
+
* - Cache miss during load: Automatic MCP refresh triggered
|
|
119
|
+
* - Timestamp drift: UTC normalization prevents timezone confusion
|
|
120
|
+
* - Concurrent workflow execution: Lock-based synchronization
|
|
121
|
+
*
|
|
122
|
+
* **SCALABILITY CONSIDERATIONS:**
|
|
123
|
+
* - State file size grows ~50KB per month (manageable)
|
|
124
|
+
* - Cache memory: ~5MB typical, scales with coverage breadth
|
|
125
|
+
* - Similarity computation: O(n) in articles, automated pruning at 180 days
|
|
126
|
+
* - MCP query cache: Automatic cleanup of expired entries
|
|
127
|
+
*
|
|
128
|
+
* **GDPR COMPLIANCE:**
|
|
129
|
+
* - Member mentions in articles tracked in state
|
|
130
|
+
* - Data retention policies enforced (180-day pruning)
|
|
131
|
+
* - Audit trail supports member rights requests
|
|
132
|
+
* - No personal data stored in cache beyond article references
|
|
133
|
+
*
|
|
134
|
+
* @osint Workflow Intelligence Analysis
|
|
135
|
+
* - Tracks which topics get covered and when
|
|
136
|
+
* - Identifies coordination patterns across workflows
|
|
137
|
+
* - Enables predictive analysis of future coverage
|
|
138
|
+
* - Supports investigation of coordination anomalies
|
|
139
|
+
*
|
|
140
|
+
* @risk Deduplication Accuracy
|
|
141
|
+
* - 70% similarity threshold prevents false positives
|
|
142
|
+
* - Enables legitimate retelling of same story (new angle)
|
|
143
|
+
* - Detects coordinated coverage (unusual pattern)
|
|
144
|
+
* - Monitors for suspicious generation patterns
|
|
145
|
+
*
|
|
146
|
+
* @gdpr Data Retention & Cleanup
|
|
147
|
+
* - Automatic pruning of state after 180 days
|
|
148
|
+
* - Member data retention tied to article dates
|
|
149
|
+
* - Supports right-to-be-forgotten implementations
|
|
150
|
+
* - Audit logging for regulatory compliance
|
|
151
|
+
*
|
|
152
|
+
* @security State Integrity
|
|
153
|
+
* - File permissions protect state from unauthorized modification
|
|
154
|
+
* - Checksums validate cache data integrity
|
|
155
|
+
* - Atomic writes prevent partial state corruption
|
|
156
|
+
* - Versioning enables rollback if needed
|
|
157
|
+
*
|
|
158
|
+
* @author Hack23 AB (Editorial Operations & Workflow Optimization)
|
|
159
|
+
* @license Apache-2.0
|
|
160
|
+
* @version 2.2.0
|
|
161
|
+
* @since 2024-10-15
|
|
162
|
+
* @see news/metadata/workflow-state.json (State Persistence)
|
|
163
|
+
* @see Issue #150 (Workflow Coordination Enhancement)
|
|
164
|
+
* @see docs/WORKFLOW_ARCHITECTURE.md (Complete Architecture)
|
|
165
|
+
*/
|
|
166
|
+
import fs from 'fs';
|
|
167
|
+
import path from 'path';
|
|
168
|
+
import { fileURLToPath } from 'url';
|
|
169
|
+
import crypto from 'crypto';
|
|
170
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
171
|
+
const __dirname = path.dirname(__filename);
|
|
172
|
+
const STATE_FILE = path.join(__dirname, '..', 'news', 'metadata', 'workflow-state.json');
|
|
173
|
+
const LOCK_DIR = path.join(__dirname, '..', 'news', 'metadata', 'locks');
|
|
174
|
+
const MCP_CACHE_TTL_SECONDS = 2 * 60 * 60; // 2 hours
|
|
175
|
+
const MCP_CACHE_TTL_NON_PLENARY_SECONDS = 4 * 60 * 60; // 4 hours
|
|
176
|
+
const RECENT_ARTICLE_TTL_SECONDS = 6 * 60 * 60; // 6 hours
|
|
177
|
+
const SIMILARITY_THRESHOLD = 0.70; // 70% similarity triggers deduplication
|
|
178
|
+
const TOPIC_JACCARD_THRESHOLD = 0.50; // 50% topic overlap triggers deduplication
|
|
179
|
+
const LOCK_TIMEOUT_MS = 45 * 60 * 1000; // 45 minutes
|
|
180
|
+
const ACTIVE_GENERATION_TTL_MS = 45 * 60 * 1000; // 45 minutes
|
|
181
|
+
const RETRIABLE_RENAME_CODES = new Set(['EEXIST', 'EPERM', 'EACCES', 'EXDEV']);
|
|
182
|
+
/** Returns true when `v` is a non-null, non-array plain object. */
|
|
183
|
+
function isPlainObject(v) {
|
|
184
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
185
|
+
}
|
|
186
|
+
const STOCKHOLM_HOUR_FORMATTER = new Intl.DateTimeFormat('en-GB', {
|
|
187
|
+
timeZone: 'Europe/Stockholm',
|
|
188
|
+
hour: '2-digit',
|
|
189
|
+
hourCycle: 'h23',
|
|
190
|
+
});
|
|
191
|
+
/**
|
|
192
|
+
* Compute Jaccard similarity between two topic arrays.
|
|
193
|
+
*
|
|
194
|
+
* @param a - First topic array
|
|
195
|
+
* @param b - Second topic array
|
|
196
|
+
* @returns Jaccard similarity 0.0-1.0
|
|
197
|
+
*/
|
|
198
|
+
export function jaccardTopicSimilarity(a, b) {
|
|
199
|
+
const setA = new Set(a.map((t) => t.toLowerCase()));
|
|
200
|
+
const setB = new Set(b.map((t) => t.toLowerCase()));
|
|
201
|
+
const intersection = [...setA].filter((t) => setB.has(t)).length;
|
|
202
|
+
const union = new Set([...setA, ...setB]).size;
|
|
203
|
+
return union === 0 ? 0 : intersection / union;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Return adaptive MCP cache TTL based on Riksdag plenary hours.
|
|
207
|
+
*
|
|
208
|
+
* Plenary hours: 08:00–16:00 Europe/Stockholm local time (DST-aware) → 2-hour TTL.
|
|
209
|
+
* Non-plenary hours → 4-hour TTL (data changes less frequently).
|
|
210
|
+
*
|
|
211
|
+
* @param now - Optional Date for testing
|
|
212
|
+
* @returns TTL in seconds
|
|
213
|
+
*/
|
|
214
|
+
export function getAdaptiveCacheTTL(now) {
|
|
215
|
+
const d = now ?? new Date();
|
|
216
|
+
const stockholmHour = Number.parseInt(STOCKHOLM_HOUR_FORMATTER.format(d), 10);
|
|
217
|
+
const isPlenaryHour = stockholmHour >= 8 && stockholmHour <= 16;
|
|
218
|
+
return isPlenaryHour ? MCP_CACHE_TTL_SECONDS : MCP_CACHE_TTL_NON_PLENARY_SECONDS;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Workflow Lock Manager — file-based soft locks for cross-workflow coordination.
|
|
222
|
+
*
|
|
223
|
+
* Locks are directories under `news/metadata/locks/{type}-{date}.lock/`
|
|
224
|
+
* containing an `info.json` file with lease metadata.
|
|
225
|
+
*/
|
|
226
|
+
export class WorkflowLockManager {
|
|
227
|
+
lockDir;
|
|
228
|
+
timeoutMs;
|
|
229
|
+
constructor(lockDir = LOCK_DIR, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
230
|
+
this.lockDir = lockDir;
|
|
231
|
+
this.timeoutMs = timeoutMs;
|
|
232
|
+
}
|
|
233
|
+
validateLockInputs(type, date) {
|
|
234
|
+
if (!/^[a-z0-9-]+$/.test(type)) {
|
|
235
|
+
throw new Error(`Invalid lock type "${type}"`);
|
|
236
|
+
}
|
|
237
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
238
|
+
throw new Error(`Invalid lock date "${date}"`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
getLockPath(type, date) {
|
|
242
|
+
this.validateLockInputs(type, date);
|
|
243
|
+
return path.join(this.lockDir, `${type}-${date}.lock`);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Acquire a soft lock for the given type + date.
|
|
247
|
+
* Uses `mkdirSync({ recursive: false })` for atomic creation on POSIX.
|
|
248
|
+
*
|
|
249
|
+
* @returns true if lock acquired, false if already held
|
|
250
|
+
*/
|
|
251
|
+
acquireLock(type, date, workflowId) {
|
|
252
|
+
const lockPath = this.getLockPath(type, date);
|
|
253
|
+
const maxReclaims = 1; // keep configurable if policy changes
|
|
254
|
+
for (let reclaimAttempts = 0; reclaimAttempts <= maxReclaims; reclaimAttempts += 1) {
|
|
255
|
+
try {
|
|
256
|
+
// Ensure parent directory exists
|
|
257
|
+
if (!fs.existsSync(this.lockDir)) {
|
|
258
|
+
fs.mkdirSync(this.lockDir, { recursive: true });
|
|
259
|
+
}
|
|
260
|
+
// Atomic directory creation — fails if already exists.
|
|
261
|
+
// Note: atomic on local POSIX filesystems; not guaranteed on NFS/distributed FS.
|
|
262
|
+
fs.mkdirSync(lockPath, { recursive: false });
|
|
263
|
+
const info = {
|
|
264
|
+
workflowId,
|
|
265
|
+
acquiredAt: new Date().toISOString(),
|
|
266
|
+
expiresAfterMs: this.timeoutMs,
|
|
267
|
+
};
|
|
268
|
+
fs.writeFileSync(path.join(lockPath, 'info.json'), JSON.stringify(info, null, 2), 'utf-8');
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
const error = err;
|
|
273
|
+
if (error?.code !== 'EEXIST') {
|
|
274
|
+
const details = [
|
|
275
|
+
'[WorkflowLockManager] Failed to acquire workflow lock',
|
|
276
|
+
`lockPath=${lockPath}`,
|
|
277
|
+
];
|
|
278
|
+
if (typeof error?.code === 'string')
|
|
279
|
+
details.push(`code=${error.code}`);
|
|
280
|
+
if (typeof error?.message === 'string')
|
|
281
|
+
details.push(`message=${error.message}`);
|
|
282
|
+
console.error(details.join(' | '));
|
|
283
|
+
throw err;
|
|
284
|
+
}
|
|
285
|
+
let reclaimed = false;
|
|
286
|
+
const infoPath = path.join(lockPath, 'info.json');
|
|
287
|
+
if (fs.existsSync(infoPath)) {
|
|
288
|
+
try {
|
|
289
|
+
const raw = fs.readFileSync(infoPath, 'utf-8');
|
|
290
|
+
const existing = JSON.parse(raw);
|
|
291
|
+
const acquiredAtMs = new Date(existing.acquiredAt).getTime();
|
|
292
|
+
const hasValidAcquiredAt = Number.isFinite(acquiredAtMs);
|
|
293
|
+
const hasExpiresAfterMs = Object.prototype.hasOwnProperty.call(existing, 'expiresAfterMs');
|
|
294
|
+
const hasValidExpiresAfterMs = hasExpiresAfterMs &&
|
|
295
|
+
typeof existing.expiresAfterMs === 'number' &&
|
|
296
|
+
Number.isFinite(existing.expiresAfterMs) &&
|
|
297
|
+
existing.expiresAfterMs > 0;
|
|
298
|
+
const expiryMs = (hasValidExpiresAfterMs && typeof existing.expiresAfterMs === 'number') ? existing.expiresAfterMs : this.timeoutMs;
|
|
299
|
+
const isExpired = hasValidAcquiredAt && Date.now() - acquiredAtMs > expiryMs;
|
|
300
|
+
const treatAsCorrupt = !hasValidAcquiredAt || (hasExpiresAfterMs && !hasValidExpiresAfterMs);
|
|
301
|
+
if ((isExpired || treatAsCorrupt) && reclaimAttempts < maxReclaims) {
|
|
302
|
+
// Stale or corrupt lock — reclaim so workflows aren't blocked indefinitely.
|
|
303
|
+
fs.rmSync(lockPath, { recursive: true, force: true });
|
|
304
|
+
reclaimed = true;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Corrupt or unreadable info.json — treat as reclaimable when within maxReclaims.
|
|
309
|
+
if (reclaimAttempts < maxReclaims) {
|
|
310
|
+
try {
|
|
311
|
+
fs.rmSync(lockPath, { recursive: true, force: true });
|
|
312
|
+
reclaimed = true;
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// If we cannot remove the corrupt lock, treat as held.
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else if (reclaimAttempts < maxReclaims) {
|
|
321
|
+
// Lock directory exists but info.json is missing — orphaned lock.
|
|
322
|
+
// Reclaim it so workflows aren't blocked indefinitely.
|
|
323
|
+
try {
|
|
324
|
+
fs.rmSync(lockPath, { recursive: true, force: true });
|
|
325
|
+
reclaimed = true;
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// If we cannot remove the orphaned lock, treat as held.
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (!reclaimed)
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Release a held lock.
|
|
339
|
+
*/
|
|
340
|
+
releaseLock(type, date) {
|
|
341
|
+
const lockPath = this.getLockPath(type, date);
|
|
342
|
+
try {
|
|
343
|
+
fs.rmSync(lockPath, { recursive: true, force: true });
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// Ignore errors during cleanup
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Check if a lock is currently held.
|
|
351
|
+
*/
|
|
352
|
+
isLocked(type, date) {
|
|
353
|
+
const lockPath = this.getLockPath(type, date);
|
|
354
|
+
return fs.existsSync(lockPath);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Read lock information if the lock exists.
|
|
358
|
+
*/
|
|
359
|
+
getLockInfo(type, date) {
|
|
360
|
+
const infoPath = path.join(this.getLockPath(type, date), 'info.json');
|
|
361
|
+
try {
|
|
362
|
+
if (fs.existsSync(infoPath)) {
|
|
363
|
+
return JSON.parse(fs.readFileSync(infoPath, 'utf-8'));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
// Ignore read errors
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Remove all locks older than the configured timeout.
|
|
373
|
+
*
|
|
374
|
+
* @returns Number of stale locks cleaned up
|
|
375
|
+
*/
|
|
376
|
+
cleanupStaleLocks() {
|
|
377
|
+
let cleaned = 0;
|
|
378
|
+
try {
|
|
379
|
+
if (!fs.existsSync(this.lockDir))
|
|
380
|
+
return 0;
|
|
381
|
+
const entries = fs.readdirSync(this.lockDir);
|
|
382
|
+
for (const entry of entries) {
|
|
383
|
+
if (!entry.endsWith('.lock'))
|
|
384
|
+
continue;
|
|
385
|
+
const lockPath = path.join(this.lockDir, entry);
|
|
386
|
+
const infoPath = path.join(lockPath, 'info.json');
|
|
387
|
+
try {
|
|
388
|
+
if (fs.existsSync(infoPath)) {
|
|
389
|
+
const info = JSON.parse(fs.readFileSync(infoPath, 'utf-8'));
|
|
390
|
+
const acquiredAtMs = new Date(info.acquiredAt).getTime();
|
|
391
|
+
const explicitExpiry = info.expiresAfterMs;
|
|
392
|
+
const hasExplicitExpiry = explicitExpiry !== undefined;
|
|
393
|
+
const expiryMs = hasExplicitExpiry && typeof explicitExpiry === 'number'
|
|
394
|
+
? explicitExpiry
|
|
395
|
+
: this.timeoutMs;
|
|
396
|
+
const isAcquiredAtFinite = Number.isFinite(acquiredAtMs);
|
|
397
|
+
const hasInvalidExplicitExpiry = hasExplicitExpiry &&
|
|
398
|
+
!(typeof explicitExpiry === 'number' && Number.isFinite(explicitExpiry) && explicitExpiry > 0);
|
|
399
|
+
// Treat invalid timestamp/expiry as corrupt and reclaimable.
|
|
400
|
+
if (!isAcquiredAtFinite ||
|
|
401
|
+
hasInvalidExplicitExpiry ||
|
|
402
|
+
Date.now() - acquiredAtMs > expiryMs) {
|
|
403
|
+
fs.rmSync(lockPath, { recursive: true, force: true });
|
|
404
|
+
cleaned++;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
// No info.json — remove orphaned lock directory
|
|
409
|
+
fs.rmSync(lockPath, { recursive: true, force: true });
|
|
410
|
+
cleaned++;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
// Treat unreadable/corrupt info.json or other per-lock errors as invalid lock;
|
|
415
|
+
// attempt best-effort removal so stale/corrupt locks do not block new workflows.
|
|
416
|
+
try {
|
|
417
|
+
fs.rmSync(lockPath, { recursive: true, force: true });
|
|
418
|
+
cleaned++;
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
// Ignore errors during lock directory removal
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// Ignore overall cleanup errors
|
|
428
|
+
}
|
|
429
|
+
return cleaned;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Workflow State Coordinator
|
|
434
|
+
*/
|
|
435
|
+
export class WorkflowStateCoordinator {
|
|
436
|
+
stateFilePath;
|
|
437
|
+
state;
|
|
438
|
+
constructor(stateFilePath = STATE_FILE) {
|
|
439
|
+
this.stateFilePath = stateFilePath;
|
|
440
|
+
this.state = {
|
|
441
|
+
lastUpdate: null,
|
|
442
|
+
recentArticles: [],
|
|
443
|
+
mcpQueryCache: {},
|
|
444
|
+
workflows: {},
|
|
445
|
+
activeGenerations: [],
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Load state from disk
|
|
450
|
+
*/
|
|
451
|
+
async load() {
|
|
452
|
+
try {
|
|
453
|
+
if (fs.existsSync(this.stateFilePath)) {
|
|
454
|
+
const content = fs.readFileSync(this.stateFilePath, 'utf-8');
|
|
455
|
+
const parsed = JSON.parse(content);
|
|
456
|
+
const raw = isPlainObject(parsed) ? parsed : {};
|
|
457
|
+
// Normalize all required fields so legacy/partial state files
|
|
458
|
+
// don't cause runtime errors when accessed later.
|
|
459
|
+
this.state = {
|
|
460
|
+
lastUpdate: typeof raw.lastUpdate === 'string' ? raw.lastUpdate : null,
|
|
461
|
+
recentArticles: Array.isArray(raw.recentArticles) ? raw.recentArticles : [],
|
|
462
|
+
mcpQueryCache: isPlainObject(raw.mcpQueryCache)
|
|
463
|
+
? raw.mcpQueryCache
|
|
464
|
+
: {},
|
|
465
|
+
workflows: isPlainObject(raw.workflows)
|
|
466
|
+
? raw.workflows
|
|
467
|
+
: {},
|
|
468
|
+
activeGenerations: Array.isArray(raw.activeGenerations) ? raw.activeGenerations : [],
|
|
469
|
+
};
|
|
470
|
+
this.cleanupExpiredEntries();
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
// Initialize empty state
|
|
474
|
+
await this.save();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
479
|
+
console.warn('Warning: Could not load workflow state:', message);
|
|
480
|
+
// Continue with empty state
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Save state to disk using atomic write (write-to-tmp + rename).
|
|
485
|
+
*/
|
|
486
|
+
async save() {
|
|
487
|
+
try {
|
|
488
|
+
const dir = path.dirname(this.stateFilePath);
|
|
489
|
+
if (!fs.existsSync(dir)) {
|
|
490
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
491
|
+
}
|
|
492
|
+
this.state.lastUpdate = new Date().toISOString();
|
|
493
|
+
const tmpPath = `${this.stateFilePath}.tmp.${process.pid}`;
|
|
494
|
+
fs.writeFileSync(tmpPath, JSON.stringify(this.state, null, 2), 'utf-8');
|
|
495
|
+
try {
|
|
496
|
+
fs.renameSync(tmpPath, this.stateFilePath);
|
|
497
|
+
}
|
|
498
|
+
catch (renameErr) {
|
|
499
|
+
// On Windows, renameSync can fail when the destination already exists.
|
|
500
|
+
// For known retriable codes, use backup-then-rename so last good state
|
|
501
|
+
// remains recoverable if retry fails.
|
|
502
|
+
const code = renameErr.code;
|
|
503
|
+
if (code && RETRIABLE_RENAME_CODES.has(code)) {
|
|
504
|
+
const backupPath = `${this.stateFilePath}.bak`;
|
|
505
|
+
let hadExisting = false;
|
|
506
|
+
try {
|
|
507
|
+
if (fs.existsSync(this.stateFilePath)) {
|
|
508
|
+
hadExisting = true;
|
|
509
|
+
if (fs.existsSync(backupPath)) {
|
|
510
|
+
fs.unlinkSync(backupPath);
|
|
511
|
+
}
|
|
512
|
+
fs.renameSync(this.stateFilePath, backupPath);
|
|
513
|
+
}
|
|
514
|
+
fs.renameSync(tmpPath, this.stateFilePath);
|
|
515
|
+
if (hadExisting) {
|
|
516
|
+
try {
|
|
517
|
+
fs.unlinkSync(backupPath);
|
|
518
|
+
}
|
|
519
|
+
catch { /* ignore backup cleanup error */ }
|
|
520
|
+
}
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
catch (retryErr) {
|
|
524
|
+
// Restore previous state if we moved it to backup but failed to write new state.
|
|
525
|
+
try {
|
|
526
|
+
if (hadExisting && !fs.existsSync(this.stateFilePath) && fs.existsSync(backupPath)) {
|
|
527
|
+
fs.renameSync(backupPath, this.stateFilePath);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
catch (restoreErr) {
|
|
531
|
+
const restoreMessage = restoreErr instanceof Error ? restoreErr.message : String(restoreErr);
|
|
532
|
+
console.warn(`Warning: Failed to restore workflow state backup at ${backupPath}: ${restoreMessage}`);
|
|
533
|
+
}
|
|
534
|
+
// Best-effort cleanup of tmp file after failed retry
|
|
535
|
+
try {
|
|
536
|
+
fs.unlinkSync(tmpPath);
|
|
537
|
+
}
|
|
538
|
+
catch { /* ignore cleanup error */ }
|
|
539
|
+
throw retryErr;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// Non-retriable rename failure: clean up tmp file and rethrow
|
|
543
|
+
try {
|
|
544
|
+
fs.unlinkSync(tmpPath);
|
|
545
|
+
}
|
|
546
|
+
catch { /* ignore cleanup error */ }
|
|
547
|
+
throw renameErr;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
552
|
+
console.error('Error saving workflow state:', message);
|
|
553
|
+
throw error;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Clean up expired cache entries and old articles
|
|
558
|
+
*/
|
|
559
|
+
cleanupExpiredEntries() {
|
|
560
|
+
const now = Date.now();
|
|
561
|
+
// Clean MCP cache using per-entry TTL (default: MCP_CACHE_TTL_SECONDS, 2 hours)
|
|
562
|
+
Object.keys(this.state.mcpQueryCache).forEach((key) => {
|
|
563
|
+
const entry = this.state.mcpQueryCache[key];
|
|
564
|
+
if (!entry) {
|
|
565
|
+
delete this.state.mcpQueryCache[key];
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const entryTime = new Date(entry.timestamp).getTime();
|
|
569
|
+
// If timestamp is invalid (NaN), treat as expired and delete
|
|
570
|
+
if (isNaN(entryTime)) {
|
|
571
|
+
delete this.state.mcpQueryCache[key];
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const effectiveTtlSeconds = typeof entry.ttl === 'number' && entry.ttl > 0
|
|
575
|
+
? entry.ttl
|
|
576
|
+
: MCP_CACHE_TTL_SECONDS;
|
|
577
|
+
if (now - entryTime > effectiveTtlSeconds * 1000) {
|
|
578
|
+
delete this.state.mcpQueryCache[key];
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
// Clean recent articles (6-hour TTL)
|
|
582
|
+
this.state.recentArticles = this.state.recentArticles.filter((article) => {
|
|
583
|
+
const articleTime = new Date(article.timestamp).getTime();
|
|
584
|
+
// If timestamp is invalid (NaN), treat as expired and exclude
|
|
585
|
+
if (isNaN(articleTime)) {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
return (now - articleTime) <= RECENT_ARTICLE_TTL_SECONDS * 1000;
|
|
589
|
+
});
|
|
590
|
+
// Clean stale active generations
|
|
591
|
+
if (this.state.activeGenerations) {
|
|
592
|
+
this.state.activeGenerations = this.state.activeGenerations.filter((generation) => {
|
|
593
|
+
const startedAt = new Date(generation.startedAt).getTime();
|
|
594
|
+
if (isNaN(startedAt)) {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
return (now - startedAt) <= ACTIVE_GENERATION_TTL_MS;
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Cache MCP query result with adaptive TTL.
|
|
603
|
+
*
|
|
604
|
+
* @param queryKey - Unique identifier for the query
|
|
605
|
+
* @param result - Query result to cache
|
|
606
|
+
* @param ttl - Time to live in seconds (default: adaptive based on plenary hours)
|
|
607
|
+
*/
|
|
608
|
+
async cacheMCPQuery(queryKey, result, ttl) {
|
|
609
|
+
const effectiveTtl = ttl ?? getAdaptiveCacheTTL();
|
|
610
|
+
const resultHash = this.hashObject(result);
|
|
611
|
+
this.state.mcpQueryCache[queryKey] = {
|
|
612
|
+
timestamp: new Date().toISOString(),
|
|
613
|
+
ttl: effectiveTtl,
|
|
614
|
+
resultHash,
|
|
615
|
+
result,
|
|
616
|
+
};
|
|
617
|
+
await this.save();
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Get cached MCP query result
|
|
621
|
+
*
|
|
622
|
+
* @param queryKey - Unique identifier for the query
|
|
623
|
+
* @returns Cached result or null if expired/missing
|
|
624
|
+
*/
|
|
625
|
+
getCachedMCPQuery(queryKey) {
|
|
626
|
+
this.cleanupExpiredEntries();
|
|
627
|
+
const entry = this.state.mcpQueryCache[queryKey];
|
|
628
|
+
if (!entry)
|
|
629
|
+
return null;
|
|
630
|
+
const now = Date.now();
|
|
631
|
+
const entryTime = new Date(entry.timestamp).getTime();
|
|
632
|
+
// Use per-entry TTL with fallback to default constant
|
|
633
|
+
const effectiveTtlSeconds = typeof entry.ttl === 'number' && entry.ttl > 0
|
|
634
|
+
? entry.ttl
|
|
635
|
+
: MCP_CACHE_TTL_SECONDS;
|
|
636
|
+
if (now - entryTime > effectiveTtlSeconds * 1000) {
|
|
637
|
+
delete this.state.mcpQueryCache[queryKey];
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
return entry.result;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Add recent article to tracking
|
|
644
|
+
*
|
|
645
|
+
* @param article - Article metadata
|
|
646
|
+
*/
|
|
647
|
+
async addRecentArticle(article) {
|
|
648
|
+
const articleEntry = {
|
|
649
|
+
slug: article.slug,
|
|
650
|
+
timestamp: new Date().toISOString(),
|
|
651
|
+
workflow: article.workflow ?? 'unknown',
|
|
652
|
+
title: article.title,
|
|
653
|
+
topics: article.topics ? [...article.topics] : [],
|
|
654
|
+
mcpQueries: article.mcpQueries ? [...article.mcpQueries] : [],
|
|
655
|
+
significance: article.significance,
|
|
656
|
+
};
|
|
657
|
+
this.state.recentArticles.push(articleEntry);
|
|
658
|
+
await this.save();
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Check if article is duplicate based on similarity.
|
|
662
|
+
*
|
|
663
|
+
* Uses both weighted title/topic/source similarity (≥ 0.70 threshold)
|
|
664
|
+
* and Jaccard topic-only similarity (≥ 0.50 threshold) to catch
|
|
665
|
+
* same-topic articles with different titles.
|
|
666
|
+
*
|
|
667
|
+
* @param title - Article title
|
|
668
|
+
* @param topics - Article topics
|
|
669
|
+
* @param mcpQueries - MCP query keys used for this article
|
|
670
|
+
* @param significance - Optional political significance score (0-100).
|
|
671
|
+
* When provided and ≥ 80, a same-topic article with lower significance
|
|
672
|
+
* is NOT treated as a duplicate — the high-significance version overrides.
|
|
673
|
+
* @returns Duplicate check result with similarity score
|
|
674
|
+
*/
|
|
675
|
+
async checkDuplicateArticle(title, topics = [], mcpQueries = [], significance) {
|
|
676
|
+
this.cleanupExpiredEntries();
|
|
677
|
+
// Track both:
|
|
678
|
+
// 1) maxSimilarity/matchedArticle: highest similarity overall (for reporting)
|
|
679
|
+
// 2) bestDuplicateScore/duplicateMatchedArticle: highest score among entries
|
|
680
|
+
// that actually satisfy duplicate criteria (combined>=0.70 OR topic>=0.50)
|
|
681
|
+
let maxSimilarity = 0;
|
|
682
|
+
let matchedArticle = null;
|
|
683
|
+
let bestDuplicateScore = -1;
|
|
684
|
+
let duplicateMatchedArticle = null;
|
|
685
|
+
let isDuplicate = false;
|
|
686
|
+
const similarMatches = [];
|
|
687
|
+
for (const recentArticle of this.state.recentArticles) {
|
|
688
|
+
// Weighted combined similarity (title 50%, topics 30%, sources 20%)
|
|
689
|
+
const combinedSimilarity = this.calculateSimilarity(title, topics, mcpQueries, recentArticle.title, [...recentArticle.topics], [...recentArticle.mcpQueries]);
|
|
690
|
+
// Jaccard topic-only similarity for semantic deduplication
|
|
691
|
+
const topicJaccard = jaccardTopicSimilarity(topics, recentArticle.topics);
|
|
692
|
+
const effectiveSimilarity = Math.max(combinedSimilarity, topicJaccard);
|
|
693
|
+
const duplicateByCombined = combinedSimilarity >= SIMILARITY_THRESHOLD;
|
|
694
|
+
const duplicateByTopic = topicJaccard >= TOPIC_JACCARD_THRESHOLD;
|
|
695
|
+
const currentIsDuplicate = duplicateByCombined || duplicateByTopic;
|
|
696
|
+
if (effectiveSimilarity > maxSimilarity) {
|
|
697
|
+
maxSimilarity = effectiveSimilarity;
|
|
698
|
+
matchedArticle = recentArticle;
|
|
699
|
+
}
|
|
700
|
+
if (currentIsDuplicate) {
|
|
701
|
+
similarMatches.push(recentArticle);
|
|
702
|
+
}
|
|
703
|
+
if (currentIsDuplicate && effectiveSimilarity > bestDuplicateScore) {
|
|
704
|
+
bestDuplicateScore = effectiveSimilarity;
|
|
705
|
+
duplicateMatchedArticle = recentArticle;
|
|
706
|
+
isDuplicate = true;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
// High-significance override: if the new article has significance ≥ 80
|
|
710
|
+
// and the matched article either has no numeric significance or a score < 80,
|
|
711
|
+
// treat the matched article as lower/unknown significance and allow the new
|
|
712
|
+
// article to be published alongside the existing one (isDuplicate = false).
|
|
713
|
+
if (isDuplicate &&
|
|
714
|
+
typeof significance === 'number' &&
|
|
715
|
+
significance >= 80 &&
|
|
716
|
+
!similarMatches.some((article) => typeof article.significance === 'number' && article.significance >= 80)) {
|
|
717
|
+
return {
|
|
718
|
+
isDuplicate: false,
|
|
719
|
+
matchedArticle: null,
|
|
720
|
+
similarityScore: maxSimilarity,
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
isDuplicate,
|
|
725
|
+
matchedArticle: isDuplicate ? (duplicateMatchedArticle ?? matchedArticle) : null,
|
|
726
|
+
similarityScore: isDuplicate ? bestDuplicateScore : maxSimilarity,
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Calculate similarity between two articles
|
|
731
|
+
*
|
|
732
|
+
* Uses weighted combination of:
|
|
733
|
+
* - Title similarity (50%)
|
|
734
|
+
* - Topic overlap (30%)
|
|
735
|
+
* - MCP query overlap (20%)
|
|
736
|
+
*
|
|
737
|
+
* @returns Similarity score 0.0-1.0
|
|
738
|
+
*/
|
|
739
|
+
calculateSimilarity(title1, topics1, mcpQueries1, title2, topics2, mcpQueries2) {
|
|
740
|
+
const titleSim = this.stringSimilarity(title1, title2);
|
|
741
|
+
const topicSim = this.setOverlap(topics1, topics2);
|
|
742
|
+
const sourceSim = this.setOverlap(mcpQueries1, mcpQueries2);
|
|
743
|
+
return (titleSim * 0.5) + (topicSim * 0.3) + (sourceSim * 0.2);
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Calculate string similarity using Jaccard similarity of word sets
|
|
747
|
+
*
|
|
748
|
+
* @param str1 - First string
|
|
749
|
+
* @param str2 - Second string
|
|
750
|
+
* @returns Similarity 0.0-1.0
|
|
751
|
+
*/
|
|
752
|
+
stringSimilarity(str1, str2) {
|
|
753
|
+
if (!str1 || !str2)
|
|
754
|
+
return 0;
|
|
755
|
+
const words1 = new Set(str1.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
756
|
+
const words2 = new Set(str2.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
757
|
+
return this.setOverlap([...words1], [...words2]);
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Calculate set overlap (Jaccard similarity)
|
|
761
|
+
*
|
|
762
|
+
* @param set1 - First set
|
|
763
|
+
* @param set2 - Second set
|
|
764
|
+
* @returns Overlap 0.0-1.0
|
|
765
|
+
*/
|
|
766
|
+
setOverlap(set1, set2) {
|
|
767
|
+
if (!set1 || !set2 || set1.length === 0 || set2.length === 0)
|
|
768
|
+
return 0;
|
|
769
|
+
const s1 = new Set(set1.map((x) => String(x).toLowerCase()));
|
|
770
|
+
const s2 = new Set(set2.map((x) => String(x).toLowerCase()));
|
|
771
|
+
const intersection = new Set([...s1].filter((x) => s2.has(x)));
|
|
772
|
+
const union = new Set([...s1, ...s2]);
|
|
773
|
+
return intersection.size / union.size;
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Hash object for cache comparison
|
|
777
|
+
*
|
|
778
|
+
* @param obj - Object to hash
|
|
779
|
+
* @returns SHA-256 hash (first 16 hex chars)
|
|
780
|
+
*/
|
|
781
|
+
hashObject(obj) {
|
|
782
|
+
// Handle null/undefined and non-object inputs safely
|
|
783
|
+
// Only use Object.keys for non-null objects, otherwise let JSON.stringify
|
|
784
|
+
// use its default behavior
|
|
785
|
+
const replacer = obj !== null && typeof obj === 'object' && !Array.isArray(obj)
|
|
786
|
+
? Object.keys(obj).sort()
|
|
787
|
+
: undefined;
|
|
788
|
+
const str = JSON.stringify(obj, replacer);
|
|
789
|
+
return crypto.createHash('sha256').update(str).digest('hex').substring(0, 16);
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Record workflow execution
|
|
793
|
+
*
|
|
794
|
+
* @param workflowName - Name of workflow
|
|
795
|
+
* @param metadata - Execution metadata
|
|
796
|
+
*/
|
|
797
|
+
async recordWorkflowExecution(workflowName, metadata = {}) {
|
|
798
|
+
if (!this.state.workflows[workflowName]) {
|
|
799
|
+
this.state.workflows[workflowName] = {
|
|
800
|
+
lastRun: null,
|
|
801
|
+
runCount: 0,
|
|
802
|
+
articlesGenerated: 0,
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
const record = this.state.workflows[workflowName];
|
|
806
|
+
record.lastRun = new Date().toISOString();
|
|
807
|
+
record.runCount++;
|
|
808
|
+
if (metadata.articlesGenerated) {
|
|
809
|
+
record.articlesGenerated += metadata.articlesGenerated;
|
|
810
|
+
}
|
|
811
|
+
await this.save();
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Register an active generation for cross-workflow visibility.
|
|
815
|
+
*/
|
|
816
|
+
async registerActiveGeneration(workflowId, type, date) {
|
|
817
|
+
this.cleanupExpiredEntries();
|
|
818
|
+
if (!this.state.activeGenerations) {
|
|
819
|
+
this.state.activeGenerations = [];
|
|
820
|
+
}
|
|
821
|
+
const exists = this.state.activeGenerations.some((g) => g.workflowId === workflowId && g.type === type && g.date === date);
|
|
822
|
+
if (exists) {
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
this.state.activeGenerations.push({
|
|
826
|
+
workflowId,
|
|
827
|
+
type,
|
|
828
|
+
date,
|
|
829
|
+
startedAt: new Date().toISOString(),
|
|
830
|
+
});
|
|
831
|
+
await this.save();
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Unregister an active generation when done.
|
|
835
|
+
*/
|
|
836
|
+
async unregisterActiveGeneration(workflowId, type, date) {
|
|
837
|
+
if (!this.state.activeGenerations)
|
|
838
|
+
return;
|
|
839
|
+
this.state.activeGenerations = this.state.activeGenerations.filter((g) => !(g.workflowId === workflowId && g.type === type && g.date === date));
|
|
840
|
+
await this.save();
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Get active generations for cross-workflow visibility.
|
|
844
|
+
*/
|
|
845
|
+
getActiveGenerations() {
|
|
846
|
+
return this.state.activeGenerations ?? [];
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Get recent articles from last N hours
|
|
850
|
+
*
|
|
851
|
+
* @param hours - Hours to look back
|
|
852
|
+
* @returns Recent articles
|
|
853
|
+
*/
|
|
854
|
+
getRecentArticles(hours = 6) {
|
|
855
|
+
this.cleanupExpiredEntries();
|
|
856
|
+
const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000);
|
|
857
|
+
return this.state.recentArticles.filter((article) => {
|
|
858
|
+
return new Date(article.timestamp) >= cutoff;
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Get workflow statistics
|
|
863
|
+
*
|
|
864
|
+
* @returns Statistics by workflow
|
|
865
|
+
*/
|
|
866
|
+
getWorkflowStatistics() {
|
|
867
|
+
return {
|
|
868
|
+
...this.state.workflows,
|
|
869
|
+
cacheSize: Object.keys(this.state.mcpQueryCache).length,
|
|
870
|
+
recentArticlesCount: this.state.recentArticles.length,
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
// Export for direct usage
|
|
875
|
+
export { MCP_CACHE_TTL_SECONDS, MCP_CACHE_TTL_NON_PLENARY_SECONDS, RECENT_ARTICLE_TTL_SECONDS, SIMILARITY_THRESHOLD, TOPIC_JACCARD_THRESHOLD, LOCK_TIMEOUT_MS, ACTIVE_GENERATION_TTL_MS, };
|
|
876
|
+
//# sourceMappingURL=workflow-state-coordinator.js.map
|