flonat-research 0.1.0
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/.claude/agents/domain-reviewer.md +336 -0
- package/.claude/agents/fixer.md +226 -0
- package/.claude/agents/paper-critic.md +370 -0
- package/.claude/agents/peer-reviewer.md +289 -0
- package/.claude/agents/proposal-reviewer.md +215 -0
- package/.claude/agents/referee2-reviewer.md +367 -0
- package/.claude/agents/references/journal-referee-profiles.md +354 -0
- package/.claude/agents/references/paper-critic/council-personas.md +77 -0
- package/.claude/agents/references/paper-critic/council-prompts.md +198 -0
- package/.claude/agents/references/peer-reviewer/report-template.md +199 -0
- package/.claude/agents/references/peer-reviewer/sa-prompts.md +260 -0
- package/.claude/agents/references/peer-reviewer/security-scan.md +188 -0
- package/.claude/agents/references/proposal-reviewer/report-template.md +144 -0
- package/.claude/agents/references/proposal-reviewer/sa-prompts.md +149 -0
- package/.claude/agents/references/referee-config.md +114 -0
- package/.claude/agents/references/referee2-reviewer/audit-checklists.md +287 -0
- package/.claude/agents/references/referee2-reviewer/report-template.md +334 -0
- package/.claude/rules/design-before-results.md +52 -0
- package/.claude/rules/ignore-agents-md.md +17 -0
- package/.claude/rules/ignore-gemini-md.md +17 -0
- package/.claude/rules/lean-claude-md.md +45 -0
- package/.claude/rules/learn-tags.md +99 -0
- package/.claude/rules/overleaf-separation.md +67 -0
- package/.claude/rules/plan-first.md +175 -0
- package/.claude/rules/read-docs-first.md +50 -0
- package/.claude/rules/scope-discipline.md +28 -0
- package/.claude/settings.json +125 -0
- package/.context/current-focus.md +33 -0
- package/.context/preferences/priorities.md +36 -0
- package/.context/preferences/task-naming.md +28 -0
- package/.context/profile.md +29 -0
- package/.context/projects/_index.md +41 -0
- package/.context/projects/papers/nudge-exp.md +22 -0
- package/.context/projects/papers/uncertainty.md +31 -0
- package/.context/resources/claude-scientific-writer-review.md +48 -0
- package/.context/resources/cunningham-multi-analyst-agents.md +104 -0
- package/.context/resources/cunningham-multilang-code-audit.md +62 -0
- package/.context/resources/google-ai-co-scientist-review.md +72 -0
- package/.context/resources/karpathy-llm-council-review.md +58 -0
- package/.context/resources/multi-coder-reliability-protocol.md +175 -0
- package/.context/resources/pedro-santanna-takeaways.md +96 -0
- package/.context/resources/venue-rankings/abs_ajg_2024.csv +1823 -0
- package/.context/resources/venue-rankings/abs_ajg_2024_econ.csv +356 -0
- package/.context/resources/venue-rankings/cabs_4_4star_theory.csv +40 -0
- package/.context/resources/venue-rankings/core_2026.csv +801 -0
- package/.context/resources/venue-rankings.md +147 -0
- package/.context/workflows/README.md +69 -0
- package/.context/workflows/daily-review.md +91 -0
- package/.context/workflows/meeting-actions.md +108 -0
- package/.context/workflows/replication-protocol.md +155 -0
- package/.context/workflows/weekly-review.md +113 -0
- package/.mcp-server-biblio/formatters.py +158 -0
- package/.mcp-server-biblio/pyproject.toml +11 -0
- package/.mcp-server-biblio/server.py +678 -0
- package/.mcp-server-biblio/sources/__init__.py +14 -0
- package/.mcp-server-biblio/sources/base.py +73 -0
- package/.mcp-server-biblio/sources/formatters.py +83 -0
- package/.mcp-server-biblio/sources/models.py +22 -0
- package/.mcp-server-biblio/sources/multi_source.py +243 -0
- package/.mcp-server-biblio/sources/openalex_source.py +183 -0
- package/.mcp-server-biblio/sources/scopus_source.py +309 -0
- package/.mcp-server-biblio/sources/wos_source.py +508 -0
- package/.mcp-server-biblio/uv.lock +896 -0
- package/.scripts/README.md +161 -0
- package/.scripts/ai_pattern_density.py +446 -0
- package/.scripts/conf +445 -0
- package/.scripts/config.py +122 -0
- package/.scripts/count_inventory.py +275 -0
- package/.scripts/daily_digest.py +288 -0
- package/.scripts/done +177 -0
- package/.scripts/extract_meeting_actions.py +223 -0
- package/.scripts/focus +176 -0
- package/.scripts/generate-codex-agents-md.py +217 -0
- package/.scripts/inbox +194 -0
- package/.scripts/notion_helpers.py +325 -0
- package/.scripts/openalex/query_helpers.py +306 -0
- package/.scripts/papers +227 -0
- package/.scripts/query +223 -0
- package/.scripts/session-history.py +201 -0
- package/.scripts/skill-health.py +516 -0
- package/.scripts/skill-log-miner.py +273 -0
- package/.scripts/sync-to-codex.sh +252 -0
- package/.scripts/task +213 -0
- package/.scripts/tasks +190 -0
- package/.scripts/week +206 -0
- package/CLAUDE.md +197 -0
- package/LICENSE +21 -0
- package/MEMORY.md +38 -0
- package/README.md +269 -0
- package/docs/agents.md +44 -0
- package/docs/bibliography-setup.md +55 -0
- package/docs/council-mode.md +36 -0
- package/docs/getting-started.md +245 -0
- package/docs/hooks.md +38 -0
- package/docs/mcp-servers.md +82 -0
- package/docs/notion-setup.md +109 -0
- package/docs/rules.md +33 -0
- package/docs/scripts.md +303 -0
- package/docs/setup-overview/setup-overview.pdf +0 -0
- package/docs/skills.md +70 -0
- package/docs/system.md +159 -0
- package/hooks/block-destructive-git.sh +66 -0
- package/hooks/context-monitor.py +114 -0
- package/hooks/postcompact-restore.py +157 -0
- package/hooks/precompact-autosave.py +181 -0
- package/hooks/promise-checker.sh +124 -0
- package/hooks/protect-source-files.sh +81 -0
- package/hooks/resume-context-loader.sh +53 -0
- package/hooks/startup-context-loader.sh +102 -0
- package/package.json +51 -0
- package/packages/cli-council/.github/workflows/claude-code-review.yml +44 -0
- package/packages/cli-council/.github/workflows/claude.yml +50 -0
- package/packages/cli-council/README.md +100 -0
- package/packages/cli-council/pyproject.toml +43 -0
- package/packages/cli-council/src/cli_council/__init__.py +19 -0
- package/packages/cli-council/src/cli_council/__main__.py +185 -0
- package/packages/cli-council/src/cli_council/backends/__init__.py +8 -0
- package/packages/cli-council/src/cli_council/backends/base.py +81 -0
- package/packages/cli-council/src/cli_council/backends/claude.py +25 -0
- package/packages/cli-council/src/cli_council/backends/codex.py +27 -0
- package/packages/cli-council/src/cli_council/backends/gemini.py +26 -0
- package/packages/cli-council/src/cli_council/checkpoint.py +212 -0
- package/packages/cli-council/src/cli_council/config.py +51 -0
- package/packages/cli-council/src/cli_council/council.py +391 -0
- package/packages/cli-council/src/cli_council/models.py +46 -0
- package/packages/llm-council/.github/workflows/claude-code-review.yml +44 -0
- package/packages/llm-council/.github/workflows/claude.yml +50 -0
- package/packages/llm-council/README.md +453 -0
- package/packages/llm-council/pyproject.toml +42 -0
- package/packages/llm-council/src/llm_council/__init__.py +23 -0
- package/packages/llm-council/src/llm_council/__main__.py +259 -0
- package/packages/llm-council/src/llm_council/checkpoint.py +193 -0
- package/packages/llm-council/src/llm_council/client.py +253 -0
- package/packages/llm-council/src/llm_council/config.py +232 -0
- package/packages/llm-council/src/llm_council/council.py +482 -0
- package/packages/llm-council/src/llm_council/models.py +46 -0
- package/packages/mcp-bibliography/MEMORY.md +31 -0
- package/packages/mcp-bibliography/_app.py +226 -0
- package/packages/mcp-bibliography/formatters.py +158 -0
- package/packages/mcp-bibliography/log/2026-03-13-2100.md +35 -0
- package/packages/mcp-bibliography/pyproject.toml +15 -0
- package/packages/mcp-bibliography/run.sh +20 -0
- package/packages/mcp-bibliography/scholarly_formatters.py +83 -0
- package/packages/mcp-bibliography/server.py +1857 -0
- package/packages/mcp-bibliography/tools/__init__.py +28 -0
- package/packages/mcp-bibliography/tools/_registry.py +19 -0
- package/packages/mcp-bibliography/tools/altmetric.py +107 -0
- package/packages/mcp-bibliography/tools/core.py +92 -0
- package/packages/mcp-bibliography/tools/dblp.py +52 -0
- package/packages/mcp-bibliography/tools/openalex.py +296 -0
- package/packages/mcp-bibliography/tools/opencitations.py +102 -0
- package/packages/mcp-bibliography/tools/openreview.py +179 -0
- package/packages/mcp-bibliography/tools/orcid.py +131 -0
- package/packages/mcp-bibliography/tools/scholarly.py +575 -0
- package/packages/mcp-bibliography/tools/unpaywall.py +63 -0
- package/packages/mcp-bibliography/tools/zenodo.py +123 -0
- package/packages/mcp-bibliography/uv.lock +711 -0
- package/scripts/setup.sh +143 -0
- package/skills/beamer-deck/SKILL.md +199 -0
- package/skills/beamer-deck/references/quality-rubric.md +54 -0
- package/skills/beamer-deck/references/review-prompts.md +106 -0
- package/skills/bib-validate/SKILL.md +261 -0
- package/skills/bib-validate/references/council-mode.md +34 -0
- package/skills/bib-validate/references/deep-verify.md +79 -0
- package/skills/bib-validate/references/fix-mode.md +36 -0
- package/skills/bib-validate/references/openalex-verification.md +45 -0
- package/skills/bib-validate/references/preprint-check.md +31 -0
- package/skills/bib-validate/references/ref-manager-crossref.md +41 -0
- package/skills/bib-validate/references/report-template.md +82 -0
- package/skills/code-archaeology/SKILL.md +141 -0
- package/skills/code-review/SKILL.md +265 -0
- package/skills/code-review/references/quality-rubric.md +67 -0
- package/skills/consolidate-memory/SKILL.md +208 -0
- package/skills/context-status/SKILL.md +126 -0
- package/skills/creation-guard/SKILL.md +230 -0
- package/skills/devils-advocate/SKILL.md +130 -0
- package/skills/devils-advocate/references/competing-hypotheses.md +83 -0
- package/skills/init-project/SKILL.md +115 -0
- package/skills/init-project-course/references/memory-and-settings.md +92 -0
- package/skills/init-project-course/references/organise-templates.md +94 -0
- package/skills/init-project-course/skill.md +147 -0
- package/skills/init-project-light/skill.md +139 -0
- package/skills/init-project-research/SKILL.md +368 -0
- package/skills/init-project-research/references/atlas-pipeline-sync.md +70 -0
- package/skills/init-project-research/references/atlas-schema.md +81 -0
- package/skills/init-project-research/references/confirmation-report.md +39 -0
- package/skills/init-project-research/references/domain-profile-template.md +104 -0
- package/skills/init-project-research/references/interview-round3.md +34 -0
- package/skills/init-project-research/references/literature-discovery.md +43 -0
- package/skills/init-project-research/references/scaffold-details.md +197 -0
- package/skills/init-project-research/templates/field-calibration.md +60 -0
- package/skills/init-project-research/templates/pipeline-manifest.md +63 -0
- package/skills/init-project-research/templates/run-all.sh +116 -0
- package/skills/init-project-research/templates/seed-files.md +337 -0
- package/skills/insights-deck/SKILL.md +151 -0
- package/skills/interview-me/SKILL.md +157 -0
- package/skills/latex/SKILL.md +141 -0
- package/skills/latex/references/latex-configs.md +183 -0
- package/skills/latex-autofix/SKILL.md +230 -0
- package/skills/latex-autofix/references/known-errors.md +183 -0
- package/skills/latex-autofix/references/quality-rubric.md +50 -0
- package/skills/latex-health-check/SKILL.md +161 -0
- package/skills/learn/SKILL.md +220 -0
- package/skills/learn/scripts/validate_skill.py +265 -0
- package/skills/lessons-learned/SKILL.md +201 -0
- package/skills/literature/SKILL.md +335 -0
- package/skills/literature/references/agent-templates.md +393 -0
- package/skills/literature/references/bibliometric-apis.md +44 -0
- package/skills/literature/references/cli-council-search.md +79 -0
- package/skills/literature/references/openalex-api-guide.md +371 -0
- package/skills/literature/references/openalex-common-queries.md +381 -0
- package/skills/literature/references/openalex-workflows.md +248 -0
- package/skills/literature/references/reference-manager-sync.md +36 -0
- package/skills/literature/references/scopus-api-guide.md +208 -0
- package/skills/literature/references/wos-api-guide.md +308 -0
- package/skills/multi-perspective/SKILL.md +311 -0
- package/skills/multi-perspective/references/computational-many-analysts.md +77 -0
- package/skills/pipeline-manifest/SKILL.md +226 -0
- package/skills/pre-submission-report/SKILL.md +153 -0
- package/skills/process-reviews/SKILL.md +244 -0
- package/skills/process-reviews/references/rr-routing.md +101 -0
- package/skills/project-deck/SKILL.md +87 -0
- package/skills/project-safety/SKILL.md +135 -0
- package/skills/proofread/SKILL.md +254 -0
- package/skills/proofread/references/quality-rubric.md +104 -0
- package/skills/python-env/SKILL.md +57 -0
- package/skills/quarto-deck/SKILL.md +226 -0
- package/skills/quarto-deck/references/markdown-format.md +143 -0
- package/skills/quarto-deck/references/quality-rubric.md +54 -0
- package/skills/save-context/SKILL.md +174 -0
- package/skills/session-log/SKILL.md +98 -0
- package/skills/shared/concept-validation-gate.md +161 -0
- package/skills/shared/council-protocol.md +265 -0
- package/skills/shared/distribution-diagnostics.md +164 -0
- package/skills/shared/engagement-stratified-sampling.md +218 -0
- package/skills/shared/escalation-protocol.md +74 -0
- package/skills/shared/external-audit-protocol.md +205 -0
- package/skills/shared/intercoder-reliability.md +256 -0
- package/skills/shared/mcp-degradation.md +81 -0
- package/skills/shared/method-probing-questions.md +163 -0
- package/skills/shared/multi-language-conventions.md +143 -0
- package/skills/shared/paid-api-safety.md +174 -0
- package/skills/shared/palettes.md +90 -0
- package/skills/shared/progressive-disclosure.md +92 -0
- package/skills/shared/project-documentation-content.md +443 -0
- package/skills/shared/project-documentation-format.md +281 -0
- package/skills/shared/project-documentation.md +100 -0
- package/skills/shared/publication-output.md +138 -0
- package/skills/shared/quality-scoring.md +70 -0
- package/skills/shared/reference-resolution.md +77 -0
- package/skills/shared/research-quality-rubric.md +165 -0
- package/skills/shared/rhetoric-principles.md +54 -0
- package/skills/shared/skill-design-patterns.md +272 -0
- package/skills/shared/skill-index.md +240 -0
- package/skills/shared/system-documentation.md +334 -0
- package/skills/shared/tikz-rules.md +402 -0
- package/skills/shared/validation-tiers.md +121 -0
- package/skills/shared/venue-guides/README.md +46 -0
- package/skills/shared/venue-guides/cell_press_style.md +483 -0
- package/skills/shared/venue-guides/conferences_formatting.md +564 -0
- package/skills/shared/venue-guides/cs_conference_style.md +463 -0
- package/skills/shared/venue-guides/examples/cell_summary_example.md +247 -0
- package/skills/shared/venue-guides/examples/medical_structured_abstract.md +313 -0
- package/skills/shared/venue-guides/examples/nature_abstract_examples.md +213 -0
- package/skills/shared/venue-guides/examples/neurips_introduction_example.md +245 -0
- package/skills/shared/venue-guides/journals_formatting.md +486 -0
- package/skills/shared/venue-guides/medical_journal_styles.md +535 -0
- package/skills/shared/venue-guides/ml_conference_style.md +556 -0
- package/skills/shared/venue-guides/nature_science_style.md +405 -0
- package/skills/shared/venue-guides/reviewer_expectations.md +417 -0
- package/skills/shared/venue-guides/venue_writing_styles.md +321 -0
- package/skills/split-pdf/SKILL.md +172 -0
- package/skills/split-pdf/methodology.md +48 -0
- package/skills/sync-notion/SKILL.md +93 -0
- package/skills/system-audit/SKILL.md +157 -0
- package/skills/system-audit/references/sub-agent-prompts.md +294 -0
- package/skills/task-management/SKILL.md +131 -0
- package/skills/update-focus/SKILL.md +204 -0
- package/skills/update-project-doc/SKILL.md +194 -0
- package/skills/validate-bib/SKILL.md +242 -0
- package/skills/validate-bib/references/council-mode.md +34 -0
- package/skills/validate-bib/references/deep-verify.md +71 -0
- package/skills/validate-bib/references/openalex-verification.md +45 -0
- package/skills/validate-bib/references/preprint-check.md +31 -0
- package/skills/validate-bib/references/report-template.md +62 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Notion API Helper Functions
|
|
3
|
+
|
|
4
|
+
Utilities for interacting with Notion databases.
|
|
5
|
+
Note: For use with Claude/Cowork, these functions describe the intended
|
|
6
|
+
operations that Claude can execute via its Notion MCP tools.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from typing import Optional, List, Dict, Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def format_task_for_notion(
|
|
14
|
+
task_name: str,
|
|
15
|
+
project: Optional[str] = None,
|
|
16
|
+
source: Optional[str] = None,
|
|
17
|
+
priority: str = "Medium",
|
|
18
|
+
due_date: Optional[str] = None,
|
|
19
|
+
task_type: Optional[List[str]] = None,
|
|
20
|
+
uni: Optional[str] = None,
|
|
21
|
+
description: Optional[str] = None,
|
|
22
|
+
) -> Dict[str, Any]:
|
|
23
|
+
"""
|
|
24
|
+
Format a task for creation in Notion Tasks Tracker.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
task_name: The task title (action verb + object + context)
|
|
28
|
+
project: Which project this belongs to (e.g., "Journal Revision")
|
|
29
|
+
source: Where the task came from (e.g., "Meeting")
|
|
30
|
+
priority: "High", "Medium", or "Low"
|
|
31
|
+
due_date: ISO format date string (YYYY-MM-DD)
|
|
32
|
+
task_type: List of types (e.g., ["📝 Writing", "📅 Meeting"])
|
|
33
|
+
uni: Related university
|
|
34
|
+
description: Additional context
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Dictionary ready for Notion create-pages API
|
|
38
|
+
"""
|
|
39
|
+
properties = {
|
|
40
|
+
"Task name": task_name,
|
|
41
|
+
"Status": "Not started",
|
|
42
|
+
"Priority": priority,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if project:
|
|
46
|
+
properties["Project"] = project
|
|
47
|
+
|
|
48
|
+
if source:
|
|
49
|
+
properties["Source"] = source
|
|
50
|
+
|
|
51
|
+
if due_date:
|
|
52
|
+
properties["date:Due date:start"] = due_date
|
|
53
|
+
properties["date:Due date:is_datetime"] = 0
|
|
54
|
+
|
|
55
|
+
if task_type:
|
|
56
|
+
properties["Task type"] = task_type
|
|
57
|
+
|
|
58
|
+
if uni:
|
|
59
|
+
properties["Uni"] = uni
|
|
60
|
+
|
|
61
|
+
if description:
|
|
62
|
+
properties["Description"] = description
|
|
63
|
+
|
|
64
|
+
return properties
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parse_deadline_from_text(text: str) -> Optional[str]:
|
|
68
|
+
"""
|
|
69
|
+
Extract deadline from natural language text.
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
"by Friday" -> next Friday's date
|
|
73
|
+
"by end of month" -> last day of current month
|
|
74
|
+
"by next Tuesday" -> next Tuesday's date
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
ISO format date string or None
|
|
78
|
+
"""
|
|
79
|
+
text_lower = text.lower()
|
|
80
|
+
today = datetime.now()
|
|
81
|
+
|
|
82
|
+
# Day names
|
|
83
|
+
days = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
|
|
84
|
+
|
|
85
|
+
for i, day in enumerate(days):
|
|
86
|
+
if day in text_lower:
|
|
87
|
+
# Calculate days until that day
|
|
88
|
+
current_day = today.weekday()
|
|
89
|
+
target_day = i
|
|
90
|
+
days_ahead = target_day - current_day
|
|
91
|
+
if days_ahead <= 0:
|
|
92
|
+
days_ahead += 7
|
|
93
|
+
if "next" in text_lower:
|
|
94
|
+
days_ahead += 7
|
|
95
|
+
target_date = today + timedelta(days=days_ahead)
|
|
96
|
+
return target_date.strftime("%Y-%m-%d")
|
|
97
|
+
|
|
98
|
+
if "end of month" in text_lower or "end of the month" in text_lower:
|
|
99
|
+
# Get last day of current month
|
|
100
|
+
if today.month == 12:
|
|
101
|
+
next_month = today.replace(year=today.year + 1, month=1, day=1)
|
|
102
|
+
else:
|
|
103
|
+
next_month = today.replace(month=today.month + 1, day=1)
|
|
104
|
+
last_day = next_month - timedelta(days=1)
|
|
105
|
+
return last_day.strftime("%Y-%m-%d")
|
|
106
|
+
|
|
107
|
+
if "end of week" in text_lower:
|
|
108
|
+
# Friday of current week
|
|
109
|
+
days_until_friday = 4 - today.weekday()
|
|
110
|
+
if days_until_friday < 0:
|
|
111
|
+
days_until_friday += 7
|
|
112
|
+
target_date = today + timedelta(days=days_until_friday)
|
|
113
|
+
return target_date.strftime("%Y-%m-%d")
|
|
114
|
+
|
|
115
|
+
if "tomorrow" in text_lower:
|
|
116
|
+
return (today + timedelta(days=1)).strftime("%Y-%m-%d")
|
|
117
|
+
|
|
118
|
+
if "today" in text_lower:
|
|
119
|
+
return today.strftime("%Y-%m-%d")
|
|
120
|
+
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def infer_project_from_context(text: str, people: List[str] = None) -> Optional[str]:
|
|
125
|
+
"""
|
|
126
|
+
Infer which project a task belongs to based on keywords and people mentioned.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
text: The task description or context
|
|
130
|
+
people: List of people mentioned
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Project name or None
|
|
134
|
+
"""
|
|
135
|
+
text_lower = text.lower()
|
|
136
|
+
|
|
137
|
+
# Keyword mapping
|
|
138
|
+
project_keywords = {
|
|
139
|
+
# Map keywords to project names for auto-categorisation
|
|
140
|
+
# "Paper Revision": ["revision", "referee", "resubmit"],
|
|
141
|
+
# "Teaching Prep": ["teaching", "students", "course", "marking"],
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# People mapping
|
|
145
|
+
people_projects = {
|
|
146
|
+
# Map collaborator names (lowercase) to projects
|
|
147
|
+
# "supervisor_name": "Paper Revision",
|
|
148
|
+
# "collaborator_name": "Joint Project",
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# Check keywords
|
|
152
|
+
for project, keywords in project_keywords.items():
|
|
153
|
+
if any(kw in text_lower for kw in keywords):
|
|
154
|
+
return project
|
|
155
|
+
|
|
156
|
+
# Check people
|
|
157
|
+
if people:
|
|
158
|
+
for person in people:
|
|
159
|
+
person_lower = person.lower()
|
|
160
|
+
for name, project in people_projects.items():
|
|
161
|
+
if name in person_lower:
|
|
162
|
+
return project
|
|
163
|
+
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def infer_priority_from_context(
|
|
168
|
+
text: str,
|
|
169
|
+
has_deadline: bool = False,
|
|
170
|
+
is_supervisor_request: bool = False,
|
|
171
|
+
is_blocking: bool = False,
|
|
172
|
+
) -> str:
|
|
173
|
+
"""
|
|
174
|
+
Infer task priority based on context clues.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
"High", "Medium", or "Low"
|
|
178
|
+
"""
|
|
179
|
+
text_lower = text.lower()
|
|
180
|
+
|
|
181
|
+
# High priority signals
|
|
182
|
+
high_signals = [
|
|
183
|
+
"urgent", "asap", "immediately", "critical", "important",
|
|
184
|
+
"deadline", "overdue", "waiting on", "blocking"
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
# Low priority signals
|
|
188
|
+
low_signals = [
|
|
189
|
+
"when you have time", "no rush", "eventually", "nice to have",
|
|
190
|
+
"optional", "someday", "maybe"
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
if any(signal in text_lower for signal in high_signals):
|
|
194
|
+
return "High"
|
|
195
|
+
|
|
196
|
+
if is_supervisor_request or is_blocking:
|
|
197
|
+
return "High"
|
|
198
|
+
|
|
199
|
+
if has_deadline:
|
|
200
|
+
return "Medium" # At least medium if there's a deadline
|
|
201
|
+
|
|
202
|
+
if any(signal in text_lower for signal in low_signals):
|
|
203
|
+
return "Low"
|
|
204
|
+
|
|
205
|
+
return "Medium" # Default
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def extract_action_patterns(text: str) -> List[Dict[str, Any]]:
|
|
209
|
+
"""
|
|
210
|
+
Extract potential action items from meeting transcript text.
|
|
211
|
+
|
|
212
|
+
Looks for patterns like:
|
|
213
|
+
- "I'll do X"
|
|
214
|
+
- "I'm going to..."
|
|
215
|
+
- "I need to..."
|
|
216
|
+
- "Can you send..."
|
|
217
|
+
- "We agreed to..."
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
List of dictionaries with 'text', 'assignee', 'type' keys
|
|
221
|
+
"""
|
|
222
|
+
actions = []
|
|
223
|
+
|
|
224
|
+
# Patterns that indicate commitments from speaker
|
|
225
|
+
commitment_patterns = [
|
|
226
|
+
r"I'll ([^.!?]+)",
|
|
227
|
+
r"I'm going to ([^.!?]+)",
|
|
228
|
+
r"I need to ([^.!?]+)",
|
|
229
|
+
r"I should ([^.!?]+)",
|
|
230
|
+
r"I can ([^.!?]+)",
|
|
231
|
+
r"I will ([^.!?]+)",
|
|
232
|
+
r"Let me ([^.!?]+)",
|
|
233
|
+
]
|
|
234
|
+
|
|
235
|
+
# Patterns that indicate requests/asks
|
|
236
|
+
request_patterns = [
|
|
237
|
+
r"[Cc]an you ([^.!?]+)",
|
|
238
|
+
r"[Cc]ould you ([^.!?]+)",
|
|
239
|
+
r"[Pp]lease ([^.!?]+)",
|
|
240
|
+
r"[Ww]ould you ([^.!?]+)",
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
# Patterns that indicate agreements
|
|
244
|
+
agreement_patterns = [
|
|
245
|
+
r"[Ww]e agreed to ([^.!?]+)",
|
|
246
|
+
r"[Ll]et's ([^.!?]+)",
|
|
247
|
+
r"[Nn]ext step is to ([^.!?]+)",
|
|
248
|
+
r"[Aa]ction item[s]?: ([^.!?]+)",
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
import re
|
|
252
|
+
|
|
253
|
+
for pattern in commitment_patterns:
|
|
254
|
+
matches = re.findall(pattern, text)
|
|
255
|
+
for match in matches:
|
|
256
|
+
actions.append({
|
|
257
|
+
"text": match.strip(),
|
|
258
|
+
"assignee": "the user",
|
|
259
|
+
"type": "commitment"
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
for pattern in request_patterns:
|
|
263
|
+
matches = re.findall(pattern, text)
|
|
264
|
+
for match in matches:
|
|
265
|
+
actions.append({
|
|
266
|
+
"text": match.strip(),
|
|
267
|
+
"assignee": "the user", # Assume requests are to the user
|
|
268
|
+
"type": "request"
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
for pattern in agreement_patterns:
|
|
272
|
+
matches = re.findall(pattern, text)
|
|
273
|
+
for match in matches:
|
|
274
|
+
actions.append({
|
|
275
|
+
"text": match.strip(),
|
|
276
|
+
"assignee": "the user",
|
|
277
|
+
"type": "agreement"
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
return actions
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# Utility functions for Claude integration
|
|
284
|
+
def create_task_prompt(action_items: List[Dict]) -> str:
|
|
285
|
+
"""
|
|
286
|
+
Generate a prompt for Claude to create tasks in Notion.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
action_items: List of extracted action items
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
Formatted prompt string
|
|
293
|
+
"""
|
|
294
|
+
if not action_items:
|
|
295
|
+
return "No action items to create."
|
|
296
|
+
|
|
297
|
+
prompt = "Please create the following tasks in the Tasks Tracker database:\n\n"
|
|
298
|
+
|
|
299
|
+
for i, item in enumerate(action_items, 1):
|
|
300
|
+
prompt += f"{i}. **{item.get('text', 'Unknown task')}**\n"
|
|
301
|
+
if item.get('deadline'):
|
|
302
|
+
prompt += f" - Due: {item['deadline']}\n"
|
|
303
|
+
if item.get('project'):
|
|
304
|
+
prompt += f" - Project: {item['project']}\n"
|
|
305
|
+
if item.get('context'):
|
|
306
|
+
prompt += f" - Context: {item['context']}\n"
|
|
307
|
+
prompt += "\n"
|
|
308
|
+
|
|
309
|
+
return prompt
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
if __name__ == "__main__":
|
|
313
|
+
# Test the functions
|
|
314
|
+
print("Testing deadline parsing:")
|
|
315
|
+
print(f" 'by Friday' -> {parse_deadline_from_text('by Friday')}")
|
|
316
|
+
print(f" 'by next Tuesday' -> {parse_deadline_from_text('by next Tuesday')}")
|
|
317
|
+
print(f" 'by end of month' -> {parse_deadline_from_text('by end of month')}")
|
|
318
|
+
|
|
319
|
+
print("\nTesting project inference:")
|
|
320
|
+
print(f" 'journal revision section 4' -> {infer_project_from_context('journal revision section 4')}")
|
|
321
|
+
print(f" 'project keyword test' -> {infer_project_from_context('project keyword test')}")
|
|
322
|
+
|
|
323
|
+
print("\nTesting priority inference:")
|
|
324
|
+
print(f" 'urgent review needed' -> {infer_priority_from_context('urgent review needed')}")
|
|
325
|
+
print(f" 'when you have time' -> {infer_priority_from_context('when you have time')}")
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Helper functions for common OpenAlex query patterns.
|
|
4
|
+
|
|
5
|
+
Provides high-level functions for typical research queries.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List, Dict, Optional, Any
|
|
9
|
+
from biblio_sources import OpenAlexClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def find_author_works(
|
|
13
|
+
author_name: str,
|
|
14
|
+
client: OpenAlexClient,
|
|
15
|
+
limit: Optional[int] = None
|
|
16
|
+
) -> List[Dict[str, Any]]:
|
|
17
|
+
"""
|
|
18
|
+
Find all works by an author (two-step pattern).
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
author_name: Author name to search for
|
|
22
|
+
client: OpenAlexClient instance
|
|
23
|
+
limit: Maximum number of works to return
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
List of works by the author
|
|
27
|
+
"""
|
|
28
|
+
# Step 1: Find author ID
|
|
29
|
+
author_response = client._make_request(
|
|
30
|
+
'/authors',
|
|
31
|
+
params={'search': author_name, 'per-page': 1}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if not author_response.get('results'):
|
|
35
|
+
print(f"No author found for: {author_name}")
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
author = author_response['results'][0]
|
|
39
|
+
author_id = author['id'].split('/')[-1] # Extract ID from URL
|
|
40
|
+
|
|
41
|
+
print(f"Found author: {author['display_name']} (ID: {author_id})")
|
|
42
|
+
|
|
43
|
+
# Step 2: Get works by author
|
|
44
|
+
works_params = {
|
|
45
|
+
'filter': f'authorships.author.id:{author_id}',
|
|
46
|
+
'per-page': 200
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if limit and limit <= 200:
|
|
50
|
+
works_params['per-page'] = limit
|
|
51
|
+
response = client._make_request('/works', works_params)
|
|
52
|
+
return response.get('results', [])
|
|
53
|
+
else:
|
|
54
|
+
# Need pagination
|
|
55
|
+
return client.paginate_all('/works', works_params, max_results=limit)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def find_institution_works(
|
|
59
|
+
institution_name: str,
|
|
60
|
+
client: OpenAlexClient,
|
|
61
|
+
limit: Optional[int] = None
|
|
62
|
+
) -> List[Dict[str, Any]]:
|
|
63
|
+
"""
|
|
64
|
+
Find all works from an institution (two-step pattern).
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
institution_name: Institution name to search for
|
|
68
|
+
client: OpenAlexClient instance
|
|
69
|
+
limit: Maximum number of works to return
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
List of works from the institution
|
|
73
|
+
"""
|
|
74
|
+
# Step 1: Find institution ID
|
|
75
|
+
inst_response = client._make_request(
|
|
76
|
+
'/institutions',
|
|
77
|
+
params={'search': institution_name, 'per-page': 1}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if not inst_response.get('results'):
|
|
81
|
+
print(f"No institution found for: {institution_name}")
|
|
82
|
+
return []
|
|
83
|
+
|
|
84
|
+
institution = inst_response['results'][0]
|
|
85
|
+
inst_id = institution['id'].split('/')[-1] # Extract ID from URL
|
|
86
|
+
|
|
87
|
+
print(f"Found institution: {institution['display_name']} (ID: {inst_id})")
|
|
88
|
+
|
|
89
|
+
# Step 2: Get works from institution
|
|
90
|
+
works_params = {
|
|
91
|
+
'filter': f'authorships.institutions.id:{inst_id}',
|
|
92
|
+
'per-page': 200
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if limit and limit <= 200:
|
|
96
|
+
works_params['per-page'] = limit
|
|
97
|
+
response = client._make_request('/works', works_params)
|
|
98
|
+
return response.get('results', [])
|
|
99
|
+
else:
|
|
100
|
+
return client.paginate_all('/works', works_params, max_results=limit)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def find_highly_cited_recent_papers(
|
|
104
|
+
topic: Optional[str] = None,
|
|
105
|
+
years: str = ">2020",
|
|
106
|
+
client: Optional[OpenAlexClient] = None,
|
|
107
|
+
limit: int = 100
|
|
108
|
+
) -> List[Dict[str, Any]]:
|
|
109
|
+
"""
|
|
110
|
+
Find highly cited recent papers, optionally filtered by topic.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
topic: Optional search term for topic filtering
|
|
114
|
+
years: Year filter (e.g., ">2020", "2020-2023")
|
|
115
|
+
client: OpenAlexClient instance
|
|
116
|
+
limit: Maximum number of papers to return
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
List of highly cited papers sorted by citation count
|
|
120
|
+
"""
|
|
121
|
+
if client is None:
|
|
122
|
+
client = OpenAlexClient()
|
|
123
|
+
|
|
124
|
+
params = {
|
|
125
|
+
'filter': f'publication_year:{years}',
|
|
126
|
+
'sort': 'cited_by_count:desc',
|
|
127
|
+
'per-page': min(limit, 200)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if topic:
|
|
131
|
+
params['search'] = topic
|
|
132
|
+
|
|
133
|
+
if limit <= 200:
|
|
134
|
+
response = client._make_request('/works', params)
|
|
135
|
+
return response.get('results', [])
|
|
136
|
+
else:
|
|
137
|
+
return client.paginate_all('/works', params, max_results=limit)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_open_access_papers(
|
|
141
|
+
search_term: str,
|
|
142
|
+
client: OpenAlexClient,
|
|
143
|
+
oa_status: str = "any", # "any", "gold", "green", "hybrid", "bronze"
|
|
144
|
+
limit: int = 100
|
|
145
|
+
) -> List[Dict[str, Any]]:
|
|
146
|
+
"""
|
|
147
|
+
Find open access papers on a topic.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
search_term: Search query
|
|
151
|
+
client: OpenAlexClient instance
|
|
152
|
+
oa_status: Type of OA ("any" for is_oa:true, or specific status)
|
|
153
|
+
limit: Maximum number of papers to return
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
List of open access papers
|
|
157
|
+
"""
|
|
158
|
+
if oa_status == "any":
|
|
159
|
+
filter_str = "is_oa:true"
|
|
160
|
+
else:
|
|
161
|
+
filter_str = f"open_access.oa_status:{oa_status}"
|
|
162
|
+
|
|
163
|
+
params = {
|
|
164
|
+
'search': search_term,
|
|
165
|
+
'filter': filter_str,
|
|
166
|
+
'per-page': min(limit, 200)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if limit <= 200:
|
|
170
|
+
response = client._make_request('/works', params)
|
|
171
|
+
return response.get('results', [])
|
|
172
|
+
else:
|
|
173
|
+
return client.paginate_all('/works', params, max_results=limit)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def get_publication_trends(
|
|
177
|
+
search_term: Optional[str] = None,
|
|
178
|
+
filter_params: Optional[Dict] = None,
|
|
179
|
+
client: Optional[OpenAlexClient] = None
|
|
180
|
+
) -> List[Dict[str, Any]]:
|
|
181
|
+
"""
|
|
182
|
+
Get publication counts by year.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
search_term: Optional search query
|
|
186
|
+
filter_params: Optional additional filters
|
|
187
|
+
client: OpenAlexClient instance
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
List of {year, count} dictionaries
|
|
191
|
+
"""
|
|
192
|
+
if client is None:
|
|
193
|
+
client = OpenAlexClient()
|
|
194
|
+
|
|
195
|
+
params = {'group_by': 'publication_year'}
|
|
196
|
+
|
|
197
|
+
if search_term:
|
|
198
|
+
params['search'] = search_term
|
|
199
|
+
|
|
200
|
+
if filter_params:
|
|
201
|
+
filter_str = ','.join([f"{k}:{v}" for k, v in filter_params.items()])
|
|
202
|
+
params['filter'] = filter_str
|
|
203
|
+
|
|
204
|
+
response = client._make_request('/works', params)
|
|
205
|
+
return response.get('group_by', [])
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def analyze_research_output(
|
|
209
|
+
entity_type: str, # 'author' or 'institution'
|
|
210
|
+
entity_name: str,
|
|
211
|
+
client: OpenAlexClient,
|
|
212
|
+
years: str = ">2020"
|
|
213
|
+
) -> Dict[str, Any]:
|
|
214
|
+
"""
|
|
215
|
+
Analyze research output for an author or institution.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
entity_type: 'author' or 'institution'
|
|
219
|
+
entity_name: Name to search for
|
|
220
|
+
client: OpenAlexClient instance
|
|
221
|
+
years: Year filter
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Dictionary with analysis results
|
|
225
|
+
"""
|
|
226
|
+
# Find entity ID
|
|
227
|
+
if entity_type == 'author':
|
|
228
|
+
endpoint = '/authors'
|
|
229
|
+
filter_prefix = 'authorships.author.id'
|
|
230
|
+
else:
|
|
231
|
+
endpoint = '/institutions'
|
|
232
|
+
filter_prefix = 'authorships.institutions.id'
|
|
233
|
+
|
|
234
|
+
# Step 1: Find entity
|
|
235
|
+
entity_response = client._make_request(
|
|
236
|
+
endpoint,
|
|
237
|
+
params={'search': entity_name, 'per-page': 1}
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if not entity_response.get('results'):
|
|
241
|
+
return {'error': f'No {entity_type} found for: {entity_name}'}
|
|
242
|
+
|
|
243
|
+
entity = entity_response['results'][0]
|
|
244
|
+
entity_id = entity['id'].split('/')[-1]
|
|
245
|
+
|
|
246
|
+
# Step 2: Get statistics
|
|
247
|
+
filter_params = {
|
|
248
|
+
filter_prefix: entity_id,
|
|
249
|
+
'publication_year': years
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Total works
|
|
253
|
+
works_response = client.search_works(
|
|
254
|
+
filter_params=filter_params,
|
|
255
|
+
per_page=1
|
|
256
|
+
)
|
|
257
|
+
total_works = works_response['meta']['count']
|
|
258
|
+
|
|
259
|
+
# Works by year
|
|
260
|
+
trends = client.group_by(
|
|
261
|
+
'works',
|
|
262
|
+
'publication_year',
|
|
263
|
+
filter_params={filter_prefix: entity_id, 'publication_year': years}
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Top topics
|
|
267
|
+
topics = client.group_by(
|
|
268
|
+
'works',
|
|
269
|
+
'topics.id',
|
|
270
|
+
filter_params=filter_params
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# OA percentage
|
|
274
|
+
oa_works = client.search_works(
|
|
275
|
+
filter_params={**filter_params, 'is_oa': 'true'},
|
|
276
|
+
per_page=1
|
|
277
|
+
)
|
|
278
|
+
oa_count = oa_works['meta']['count']
|
|
279
|
+
oa_percentage = (oa_count / total_works * 100) if total_works > 0 else 0
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
'entity_name': entity['display_name'],
|
|
283
|
+
'entity_id': entity_id,
|
|
284
|
+
'total_works': total_works,
|
|
285
|
+
'open_access_works': oa_count,
|
|
286
|
+
'open_access_percentage': round(oa_percentage, 1),
|
|
287
|
+
'publications_by_year': trends[:10], # Last 10 years
|
|
288
|
+
'top_topics': topics[:10] # Top 10 topics
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
# Example usage
|
|
294
|
+
import json
|
|
295
|
+
|
|
296
|
+
client = OpenAlexClient(email="your-email@example.com")
|
|
297
|
+
|
|
298
|
+
# Find works by author
|
|
299
|
+
print("\n=== Finding works by author ===")
|
|
300
|
+
works = find_author_works("Einstein", client, limit=5)
|
|
301
|
+
print(f"Found {len(works)} works")
|
|
302
|
+
|
|
303
|
+
# Analyze research output
|
|
304
|
+
print("\n=== Analyzing institution research output ===")
|
|
305
|
+
analysis = analyze_research_output('institution', 'MIT', client)
|
|
306
|
+
print(json.dumps(analysis, indent=2))
|