pub-analyzer 0.4.1__py3-none-any.whl → 0.4.3__py3-none-any.whl
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.
Potentially problematic release.
This version of pub-analyzer might be problematic. Click here for more details.
- pub_analyzer/css/report.tcss +5 -9
- pub_analyzer/internal/report.py +2 -2
- pub_analyzer/internal/templates/author/author_summary.typ +1 -1
- pub_analyzer/internal/templates/author/report.typ +32 -13
- pub_analyzer/internal/templates/author/sources.typ +1 -1
- pub_analyzer/internal/templates/author/works.typ +18 -18
- pub_analyzer/internal/templates/author/works_extended.typ +5 -5
- pub_analyzer/main.py +5 -2
- pub_analyzer/models/author.py +9 -2
- pub_analyzer/models/institution.py +6 -1
- pub_analyzer/models/work.py +2 -2
- pub_analyzer/widgets/author/core.py +1 -1
- pub_analyzer/widgets/author/tables.py +25 -0
- pub_analyzer/widgets/common/filters.py +1 -1
- pub_analyzer/widgets/institution/core.py +1 -1
- pub_analyzer/widgets/report/author.py +12 -8
- pub_analyzer/widgets/report/cards.py +2 -2
- pub_analyzer/widgets/report/work.py +2 -2
- pub_analyzer/widgets/search/results.py +7 -1
- {pub_analyzer-0.4.1.dist-info → pub_analyzer-0.4.3.dist-info}/METADATA +4 -3
- {pub_analyzer-0.4.1.dist-info → pub_analyzer-0.4.3.dist-info}/RECORD +24 -24
- {pub_analyzer-0.4.1.dist-info → pub_analyzer-0.4.3.dist-info}/WHEEL +1 -1
- {pub_analyzer-0.4.1.dist-info → pub_analyzer-0.4.3.dist-info}/LICENSE +0 -0
- {pub_analyzer-0.4.1.dist-info → pub_analyzer-0.4.3.dist-info}/entry_points.txt +0 -0
pub_analyzer/css/report.tcss
CHANGED
|
@@ -15,6 +15,10 @@ $primary-color-highlight: #dc2626;
|
|
|
15
15
|
ReportWidget {
|
|
16
16
|
height: 1fr;
|
|
17
17
|
margin: 1 2;
|
|
18
|
+
|
|
19
|
+
#main-container {
|
|
20
|
+
height: 100%;
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
.-dark-mode ReportWidget {
|
|
@@ -22,14 +26,6 @@ ReportWidget {
|
|
|
22
26
|
color: $text-primary-color-darken;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
ReportWidget #main-container {
|
|
26
|
-
height: 100%;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
ContentSwitcher {
|
|
30
|
-
height: 1fr;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
29
|
/* Common */
|
|
34
30
|
ReportWidget .cards-container {
|
|
35
31
|
height: auto;
|
|
@@ -37,7 +33,7 @@ ReportWidget .cards-container {
|
|
|
37
33
|
|
|
38
34
|
layout: grid;
|
|
39
35
|
grid-size: 3 1;
|
|
40
|
-
grid-rows:
|
|
36
|
+
grid-rows: 13;
|
|
41
37
|
grid-columns: 1fr;
|
|
42
38
|
grid-gutter: 1 2;
|
|
43
39
|
}
|
pub_analyzer/internal/report.py
CHANGED
|
@@ -70,7 +70,7 @@ def _get_institution_keys(
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def _get_authors_list(authorships: list[Authorship]) -> list[str]:
|
|
73
|
-
"""Collect OpenAlex IDs from authors in a list of
|
|
73
|
+
"""Collect OpenAlex IDs from authors in a list of authorship's.
|
|
74
74
|
|
|
75
75
|
Args:
|
|
76
76
|
authorships: List of authorships.
|
|
@@ -108,7 +108,7 @@ def _get_citation_type(original_work_authors: list[str], cited_work_authors: lis
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def _add_work_abstract(work: dict[str, Any]) -> dict[str, Any]:
|
|
111
|
-
"""Get work
|
|
111
|
+
"""Get work abstract from abstract_inverted_index and insert new key `abstract`.
|
|
112
112
|
|
|
113
113
|
Args:
|
|
114
114
|
work: Raw work.
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
plot.plot(
|
|
96
96
|
size: (0.90, 0.48),
|
|
97
97
|
axis-style: "scientific-auto",
|
|
98
|
-
plot-style: (stroke: (1pt +
|
|
98
|
+
plot-style: (stroke: (1pt + PALETTE.at(0)),),
|
|
99
99
|
x-min: auto, x-max: auto,
|
|
100
100
|
x-tick-step: 1, y-tick-step: auto,
|
|
101
101
|
x-label: none, y-label: none,
|
|
@@ -1,25 +1,43 @@
|
|
|
1
|
-
// This document was generated using Pub Analyzer.
|
|
2
|
-
//
|
|
1
|
+
// This document was generated using Pub Analyzer version {{ version }}.
|
|
2
|
+
//
|
|
3
|
+
// Pub Analyzer is a tool designed to retrieve, process and present in a concise and understandable
|
|
4
|
+
// way the scientific production of a researcher, including detailed information about their articles,
|
|
5
|
+
// citations, collaborations and other relevant metrics.
|
|
6
|
+
//
|
|
7
|
+
// See more here: https://pub-analyzer.com
|
|
3
8
|
|
|
4
9
|
// Packages
|
|
10
|
+
//
|
|
11
|
+
// This document uses the Cetz package to render plots and graphs. For more information
|
|
12
|
+
// on how to edit the plots see: https://typst.app/universe/package/cetz/
|
|
13
|
+
|
|
5
14
|
#import "@preview/cetz:0.2.2": canvas, plot, chart, palette
|
|
6
15
|
|
|
7
16
|
// Colors
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#let
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
//
|
|
18
|
+
// The following variables control all colors used in the document.
|
|
19
|
+
// You can modify the color codes by specifying the four RGB(A) components or by
|
|
20
|
+
// using the hexadecimal code.
|
|
21
|
+
//
|
|
22
|
+
// See more here: https://typst.app/docs/reference/visualize/color/#definitions-rgb
|
|
23
|
+
|
|
24
|
+
#let SUCCESS = rgb("#909d63")
|
|
25
|
+
#let ERROR = rgb("#bc5653")
|
|
26
|
+
|
|
27
|
+
#let CATEGORY_1 = rgb("#42a2f8")
|
|
28
|
+
#let CATEGORY_2 = rgb("#82d452")
|
|
29
|
+
#let CATEGORY_3 = rgb("#929292")
|
|
30
|
+
#let CATEGORY_4 = rgb("#f0bb40")
|
|
31
|
+
#let CATEGORY_5 = rgb("#eb4025")
|
|
32
|
+
#let CATEGORY_6 = rgb("#c33375")
|
|
33
|
+
|
|
34
|
+
#let PALETTE = (CATEGORY_1, CATEGORY_2, CATEGORY_3, CATEGORY_4, CATEGORY_5, CATEGORY_6)
|
|
35
|
+
|
|
36
|
+
// Page Layout
|
|
18
37
|
#set page("us-letter")
|
|
19
38
|
#set page(flipped: true)
|
|
20
39
|
|
|
21
40
|
#set heading(numbering: "1.")
|
|
22
|
-
|
|
23
41
|
#set page(footer: grid(
|
|
24
42
|
columns: (1fr, 1fr),
|
|
25
43
|
align(left)[Made with #link("https://pub-analyzer.com")[_pub-analyzer_] version {{ version }}],
|
|
@@ -50,6 +68,7 @@
|
|
|
50
68
|
#grid(
|
|
51
69
|
columns: (1fr),
|
|
52
70
|
row-gutter: 11pt,
|
|
71
|
+
|
|
53
72
|
[#align(center, text(size: 17pt, weight: "bold")[{{ report.author.display_name }}])],
|
|
54
73
|
{% if report.author.last_known_institutions %}
|
|
55
74
|
{% set last_known_institution = report.author.last_known_institutions[0] %}
|
|
@@ -17,6 +17,6 @@
|
|
|
17
17
|
[{{ source.issn_l or "-" }}],
|
|
18
18
|
[{{ source.summary_stats.two_yr_mean_citedness|round(3) }}],
|
|
19
19
|
[{{ source.summary_stats.h_index }}],
|
|
20
|
-
[{% if source.is_oa %}#text(rgb(
|
|
20
|
+
[{% if source.is_oa %}#text(rgb(SUCCESS))[True]{% else %}#text(rgb(ERROR))[False]{% endif %}],
|
|
21
21
|
{% endfor %}
|
|
22
22
|
)
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
{{ report.citation_summary.type_b_count }} // Type B
|
|
35
35
|
),
|
|
36
36
|
radius: 1,
|
|
37
|
-
slice-style: (
|
|
37
|
+
slice-style: (PALETTE.at(0), PALETTE.at(1)),
|
|
38
38
|
outer-label: (content: "%", radius: 115%),
|
|
39
39
|
)
|
|
40
40
|
})
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
grid.cell(colspan: 2)[
|
|
49
49
|
*Count:* {{ report.citation_summary.type_a_count + report.citation_summary.type_b_count }}
|
|
50
50
|
],
|
|
51
|
-
[#box(height: 7pt, width: 7pt, fill:
|
|
52
|
-
[#box(height: 7pt, width: 7pt, fill:
|
|
51
|
+
[#box(height: 7pt, width: 7pt, fill: PALETTE.at(0)) *Type A:* {{ report.citation_summary.type_a_count }}],
|
|
52
|
+
[#box(height: 7pt, width: 7pt, fill: PALETTE.at(1)) *Type B:* {{ report.citation_summary.type_b_count }}],
|
|
53
53
|
)
|
|
54
54
|
]
|
|
55
55
|
],
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
y-grid: false,
|
|
62
62
|
bar-style: palette.new(
|
|
63
63
|
base: (stroke: none, fill: none),
|
|
64
|
-
colors:
|
|
64
|
+
colors: PALETTE
|
|
65
65
|
),
|
|
66
66
|
(
|
|
67
67
|
{% for work_type in report.works_type_summary[:4] %}
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
|
|
84
84
|
{% for work_type in report.works_type_summary[:4] %}
|
|
85
85
|
[
|
|
86
|
-
#box(height: 7pt, width: 7pt, fill:
|
|
86
|
+
#box(height: 7pt, width: 7pt, fill: PALETTE.at({{ loop.index0 }})) *{{ work_type.type_name|capitalize }}:* {{ work_type.count }}
|
|
87
87
|
],
|
|
88
88
|
{% endfor %}
|
|
89
89
|
)
|
|
@@ -95,15 +95,15 @@
|
|
|
95
95
|
chart.piechart(
|
|
96
96
|
(
|
|
97
97
|
{{report.open_access_summary.diamond}}, // diamond
|
|
98
|
-
{{report.open_access_summary.gold}},
|
|
99
|
-
{{report.open_access_summary.green}},
|
|
100
|
-
{{report.open_access_summary.hybrid}},
|
|
101
|
-
{{report.open_access_summary.bronze}},
|
|
102
|
-
{{report.open_access_summary.closed}},
|
|
98
|
+
{{report.open_access_summary.gold}}, // Gold
|
|
99
|
+
{{report.open_access_summary.green}}, // Green
|
|
100
|
+
{{report.open_access_summary.hybrid}}, // Hybrid
|
|
101
|
+
{{report.open_access_summary.bronze}}, // Bronze
|
|
102
|
+
{{report.open_access_summary.closed}}, // Closed
|
|
103
103
|
),
|
|
104
104
|
radius: 1,
|
|
105
105
|
inner-radius: .4,
|
|
106
|
-
slice-style: (
|
|
106
|
+
slice-style: (PALETTE.at(0), PALETTE.at(3), PALETTE.at(1), PALETTE.at(4), PALETTE.at(5), PALETTE.at(2)),
|
|
107
107
|
outer-label: (content: "%", radius: 115%),
|
|
108
108
|
)
|
|
109
109
|
})
|
|
@@ -118,13 +118,12 @@
|
|
|
118
118
|
*Count:* {{ report.open_access_summary.model_dump().items()|sum(attribute="1") }}
|
|
119
119
|
],
|
|
120
120
|
|
|
121
|
-
[#box(height: 7pt, width: 7pt, fill:
|
|
122
|
-
[#box(height: 7pt, width: 7pt, fill:
|
|
123
|
-
[#box(height: 7pt, width: 7pt, fill:
|
|
124
|
-
[#box(height: 7pt, width: 7pt, fill:
|
|
125
|
-
|
|
126
|
-
[#box(height: 7pt, width: 7pt, fill:
|
|
127
|
-
[#box(height: 7pt, width: 7pt, fill: RED) *Hybrid:* {{report.open_access_summary.hybrid}}],
|
|
121
|
+
[#box(height: 7pt, width: 7pt, fill: PALETTE.at(0)) *Diamond:* {{report.open_access_summary.diamond}}],
|
|
122
|
+
[#box(height: 7pt, width: 7pt, fill: PALETTE.at(3)) *Gold:* {{report.open_access_summary.gold}}],
|
|
123
|
+
[#box(height: 7pt, width: 7pt, fill: PALETTE.at(1)) *Green:* {{report.open_access_summary.green}}],
|
|
124
|
+
[#box(height: 7pt, width: 7pt, fill: PALETTE.at(5)) *Bronze:* {{report.open_access_summary.bronze}}],
|
|
125
|
+
[#box(height: 7pt, width: 7pt, fill: PALETTE.at(2)) *Closed:* {{report.open_access_summary.closed}}],
|
|
126
|
+
[#box(height: 7pt, width: 7pt, fill: PALETTE.at(4)) *Hybrid:* {{report.open_access_summary.hybrid}}],
|
|
128
127
|
)
|
|
129
128
|
]
|
|
130
129
|
],
|
|
@@ -149,6 +148,7 @@
|
|
|
149
148
|
[{{ work.citation_summary.type_a_count }}],
|
|
150
149
|
[{{ work.citation_summary.type_b_count }}],
|
|
151
150
|
[{{ work.work.open_access.oa_status.value }}],
|
|
151
|
+
|
|
152
152
|
{% endfor %}
|
|
153
153
|
)
|
|
154
154
|
#pagebreak()
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
#linebreak()
|
|
10
10
|
|
|
11
11
|
{% if work.work.abstract %}
|
|
12
|
-
#text()[#"{{ work.work.abstract.replace('"', '\\"') }}"]
|
|
12
|
+
#text()[#"{{ work.work.abstract.replace('"', '\\"')|truncate(1000) }}"]
|
|
13
13
|
|
|
14
14
|
#linebreak()
|
|
15
15
|
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
#align(center)[_Authorships_]
|
|
24
24
|
#parbreak()
|
|
25
25
|
{% for authorship in work.work.authorships[:10] %}
|
|
26
|
-
- *{{ authorship.author_position }}:* #underline([#link("{{ authorship.author.orcid or authorship.author.id }}")[#text({% if authorship.author.display_name == report.author.display_name %}rgb(
|
|
26
|
+
- *{{ authorship.author_position }}:* #underline([#link("{{ authorship.author.orcid or authorship.author.id }}")[#text({% if authorship.author.display_name == report.author.display_name %}rgb(SUCCESS){% endif %})[{{ authorship.author.display_name }}]]])
|
|
27
27
|
{% endfor %}
|
|
28
28
|
{% if work.work.authorships|length > 10 %}
|
|
29
29
|
- *...*
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
[#"{{ cited_by_work.work.title.replace('"', '\\"') }}"],
|
|
63
63
|
[{{ cited_by_work.work.type }}],
|
|
64
64
|
[{% if cited_by_work.work.ids.doi %}#underline([#link("{{ cited_by_work.work.ids.doi }}")[DOI]]){% else %}-{% endif %}],
|
|
65
|
-
[{% if cited_by_work.citation_type.value == 0 %}#text(rgb(
|
|
65
|
+
[{% if cited_by_work.citation_type.value == 0 %}#text(rgb(SUCCESS))[Type A]{% else %}#text(rgb(ERROR))[Type B]{% endif %}],
|
|
66
66
|
[{{ cited_by_work.work.publication_date }}],
|
|
67
67
|
[{{ cited_by_work.work.cited_by_count }}],
|
|
68
68
|
{% endfor %}
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
[{{ location.source.host_organization_name or "-" }}],
|
|
88
88
|
[{{ location.source.type }}],
|
|
89
89
|
[{{ location.source.issn_l or "-" }}],
|
|
90
|
-
[{% if location.is_oa %}#text(rgb(
|
|
90
|
+
[{% if location.is_oa %}#text(rgb(SUCCESS))[True]{% else %}#text(rgb(ERROR))[False]{% endif %}],
|
|
91
91
|
[{{ location.license or "-" }}],
|
|
92
92
|
[{{ location.version.name or "-" }}],
|
|
93
93
|
{% else %}
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
[-],
|
|
97
97
|
[-],
|
|
98
98
|
[-],
|
|
99
|
-
[{% if location.is_oa %}#text(rgb(
|
|
99
|
+
[{% if location.is_oa %}#text(rgb(SUCCESS))[True]{% else %}#text(rgb(ERROR))[False]{% endif %}],
|
|
100
100
|
[{{ location.license or "-" }}],
|
|
101
101
|
[{{ location.version.name or "-" }}],
|
|
102
102
|
{% endif %}
|
pub_analyzer/main.py
CHANGED
|
@@ -4,6 +4,7 @@ import urllib.parse
|
|
|
4
4
|
import webbrowser
|
|
5
5
|
from typing import ClassVar
|
|
6
6
|
|
|
7
|
+
from textual import log
|
|
7
8
|
from textual._path import CSSPathType
|
|
8
9
|
from textual.app import App, ComposeResult
|
|
9
10
|
from textual.binding import Binding, BindingType
|
|
@@ -70,9 +71,11 @@ class PubAnalyzerApp(App[DOMNode]):
|
|
|
70
71
|
|
|
71
72
|
def action_open_link(self, link: str) -> None:
|
|
72
73
|
"""Open a link in the browser."""
|
|
73
|
-
|
|
74
|
-
if link:
|
|
74
|
+
log.info(f"Opening link: {link}")
|
|
75
|
+
if link and (link != "None"):
|
|
75
76
|
webbrowser.open(urllib.parse.unquote(link))
|
|
77
|
+
else:
|
|
78
|
+
log.warning("Link cannot be empty!")
|
|
76
79
|
|
|
77
80
|
|
|
78
81
|
def run() -> None:
|
pub_analyzer/models/author.py
CHANGED
|
@@ -39,6 +39,13 @@ class AuthorSummaryStats(BaseModel):
|
|
|
39
39
|
i10_index: int
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
class AuthorAffiliation(BaseModel):
|
|
43
|
+
"""List of affiliations this author has claimed in their publications."""
|
|
44
|
+
|
|
45
|
+
institution: DehydratedInstitution
|
|
46
|
+
years: list[int]
|
|
47
|
+
|
|
48
|
+
|
|
42
49
|
class Author(BaseModel):
|
|
43
50
|
"""Author Model Object from OpenAlex API definition."""
|
|
44
51
|
|
|
@@ -48,13 +55,13 @@ class Author(BaseModel):
|
|
|
48
55
|
|
|
49
56
|
display_name: str
|
|
50
57
|
display_name_alternatives: list[str]
|
|
58
|
+
last_known_institutions: list[DehydratedInstitution]
|
|
59
|
+
affiliations: list[AuthorAffiliation]
|
|
51
60
|
|
|
52
61
|
works_count: int
|
|
53
62
|
cited_by_count: int
|
|
54
63
|
|
|
55
|
-
last_known_institutions: list[DehydratedInstitution]
|
|
56
64
|
counts_by_year: list[AuthorYearCount]
|
|
57
|
-
|
|
58
65
|
summary_stats: AuthorSummaryStats
|
|
59
66
|
|
|
60
67
|
works_api_url: str
|
|
@@ -13,7 +13,7 @@ InstitutionOpenAlexKey: TypeAlias = str
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class InstitutionIDs(BaseModel):
|
|
16
|
-
"""IDs from
|
|
16
|
+
"""IDs from an Institution."""
|
|
17
17
|
|
|
18
18
|
openalex: InstitutionOpenAlexID
|
|
19
19
|
grid: str | None = None
|
|
@@ -32,6 +32,7 @@ class InstitutionType(str, Enum):
|
|
|
32
32
|
Nonprofit = "nonprofit"
|
|
33
33
|
Government = "government"
|
|
34
34
|
Facility = "facility"
|
|
35
|
+
Funder = "funder"
|
|
35
36
|
Other = "other"
|
|
36
37
|
|
|
37
38
|
|
|
@@ -56,10 +57,14 @@ class InstitutionGeo(BaseModel):
|
|
|
56
57
|
|
|
57
58
|
city: str
|
|
58
59
|
geonames_city_id: str
|
|
60
|
+
|
|
59
61
|
region: str | None = None
|
|
60
62
|
country_code: str
|
|
61
63
|
country: str
|
|
62
64
|
|
|
65
|
+
latitude: float
|
|
66
|
+
longitude: float
|
|
67
|
+
|
|
63
68
|
|
|
64
69
|
class InstitutionRoleType(str, Enum):
|
|
65
70
|
"""Possible institution roles."""
|
pub_analyzer/models/work.py
CHANGED
|
@@ -71,7 +71,7 @@ class ArticleProcessingCharge(BaseModel):
|
|
|
71
71
|
|
|
72
72
|
value: int
|
|
73
73
|
currency: str
|
|
74
|
-
provenance: str
|
|
74
|
+
provenance: str | None = None
|
|
75
75
|
value_usd: int | None
|
|
76
76
|
|
|
77
77
|
|
|
@@ -144,5 +144,5 @@ class Work(BaseModel):
|
|
|
144
144
|
|
|
145
145
|
@field_validator("authorships", mode="before")
|
|
146
146
|
def valid_authorships(cls, authorships: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
147
|
-
"""Skip
|
|
147
|
+
"""Skip authorship's that do not contain enough data."""
|
|
148
148
|
return [authorship for authorship in authorships if authorship["author"].get("id") is not None]
|
|
@@ -38,7 +38,7 @@ class _AuthorSummaryWidget(Static):
|
|
|
38
38
|
yield IdentifiersCard(author=self.author)
|
|
39
39
|
yield CitationMetricsCard(author=self.author)
|
|
40
40
|
|
|
41
|
-
# Work
|
|
41
|
+
# Work related info
|
|
42
42
|
with Vertical(classes="block-container"):
|
|
43
43
|
yield Label("[bold]Work Info:[/bold]", classes="block-title")
|
|
44
44
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Author Tables Widgets."""
|
|
2
2
|
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
|
|
3
5
|
from rich.table import Table
|
|
4
6
|
from textual.app import ComposeResult
|
|
5
7
|
from textual.widgets import Static
|
|
@@ -22,3 +24,26 @@ class AuthorWorksByYearTable(Static):
|
|
|
22
24
|
table.add_row(str(year), str(works_count), str(cited_by_count))
|
|
23
25
|
|
|
24
26
|
yield Static(table)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AffiliationsTable(Static):
|
|
30
|
+
"""Table with all the institutions to which an author has been affiliated."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, author: Author) -> None:
|
|
33
|
+
self.author = author
|
|
34
|
+
super().__init__()
|
|
35
|
+
|
|
36
|
+
def compose(self) -> ComposeResult:
|
|
37
|
+
"""Compose Table."""
|
|
38
|
+
table = Table("Institution", "Country", "Type", "Years", title="Affiliations", expand=True, show_lines=True)
|
|
39
|
+
for affiliation in self.author.affiliations:
|
|
40
|
+
institution = affiliation.institution
|
|
41
|
+
institution_name = (
|
|
42
|
+
f"""[@click=app.open_link("{quote(str(institution.ror))}")]{institution.display_name}[/]"""
|
|
43
|
+
if institution.ror
|
|
44
|
+
else f"{institution.display_name}"
|
|
45
|
+
)
|
|
46
|
+
years = ",".join([str(year) for year in affiliation.years])
|
|
47
|
+
table.add_row(str(institution_name), str(institution.country_code.upper()), str(institution.type.name), str(years))
|
|
48
|
+
|
|
49
|
+
yield Static(table)
|
|
@@ -38,7 +38,7 @@ class _InstitutionSummaryWidget(Static):
|
|
|
38
38
|
yield IdentifiersCard(institution=self.institution)
|
|
39
39
|
yield CitationMetricsCard(institution=self.institution)
|
|
40
40
|
|
|
41
|
-
# Work
|
|
41
|
+
# Work related info
|
|
42
42
|
with Vertical(classes="block-container"):
|
|
43
43
|
yield Label("[bold]Work Info:[/bold]", classes="block-title")
|
|
44
44
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"""Author Report Widgets."""
|
|
2
2
|
|
|
3
3
|
from textual.app import ComposeResult
|
|
4
|
-
from textual.containers import
|
|
4
|
+
from textual.containers import Horizontal, VerticalScroll
|
|
5
|
+
from textual.widgets import TabbedContent, TabPane
|
|
5
6
|
|
|
6
7
|
from pub_analyzer.models.report import AuthorReport
|
|
7
8
|
from pub_analyzer.widgets.author.cards import CitationMetricsCard, IdentifiersCard, LastInstitutionCard
|
|
8
|
-
from pub_analyzer.widgets.author.tables import AuthorWorksByYearTable
|
|
9
|
+
from pub_analyzer.widgets.author.tables import AffiliationsTable, AuthorWorksByYearTable
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class AuthorReportPane(VerticalScroll):
|
|
@@ -16,11 +17,11 @@ class AuthorReportPane(VerticalScroll):
|
|
|
16
17
|
layout: vertical;
|
|
17
18
|
overflow-x: hidden;
|
|
18
19
|
overflow-y: auto;
|
|
19
|
-
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
.author-tables-container {
|
|
22
|
+
margin: 1 0 0 0 ;
|
|
23
|
+
height: auto;
|
|
24
|
+
}
|
|
24
25
|
}
|
|
25
26
|
"""
|
|
26
27
|
|
|
@@ -35,5 +36,8 @@ class AuthorReportPane(VerticalScroll):
|
|
|
35
36
|
yield IdentifiersCard(author=self.report.author)
|
|
36
37
|
yield CitationMetricsCard(author=self.report.author)
|
|
37
38
|
|
|
38
|
-
with
|
|
39
|
-
|
|
39
|
+
with TabbedContent(id="author-tables-container"):
|
|
40
|
+
with TabPane("Citation Metrics"):
|
|
41
|
+
yield AuthorWorksByYearTable(author=self.report.author)
|
|
42
|
+
with TabPane("Institutions"):
|
|
43
|
+
yield AffiliationsTable(author=self.report.author)
|
|
@@ -68,7 +68,7 @@ class OpenAccessSummaryCard(Card):
|
|
|
68
68
|
|
|
69
69
|
# Work Info cards.
|
|
70
70
|
class AuthorshipCard(Card):
|
|
71
|
-
"""Card that enumerate the
|
|
71
|
+
"""Card that enumerate the authorship's of a work."""
|
|
72
72
|
|
|
73
73
|
def __init__(self, work: Work, author: Author | None) -> None:
|
|
74
74
|
self.work = work
|
|
@@ -77,7 +77,7 @@ class AuthorshipCard(Card):
|
|
|
77
77
|
|
|
78
78
|
def compose(self) -> ComposeResult:
|
|
79
79
|
"""Compose card."""
|
|
80
|
-
yield Label("[italic]
|
|
80
|
+
yield Label("[italic]Authorship's[/italic]", classes="card-title")
|
|
81
81
|
|
|
82
82
|
with VerticalScroll(classes="card-container"):
|
|
83
83
|
for authorship in self.work.authorships:
|
|
@@ -101,7 +101,7 @@ class WorkModal(Modal[None]):
|
|
|
101
101
|
|
|
102
102
|
# Cards
|
|
103
103
|
with Horizontal(classes="cards-container"):
|
|
104
|
-
#
|
|
104
|
+
# Authorship's
|
|
105
105
|
yield AuthorshipCard(work=self.work_report.work, author=self.author)
|
|
106
106
|
|
|
107
107
|
# OpenAccess Info
|
|
@@ -111,7 +111,7 @@ class WorkModal(Modal[None]):
|
|
|
111
111
|
yield CitationMetricsCard(work_report=self.work_report)
|
|
112
112
|
|
|
113
113
|
with TabbedContent(id="tables-container"):
|
|
114
|
-
#
|
|
114
|
+
# Abstract if exists
|
|
115
115
|
if self.work_report.work.abstract:
|
|
116
116
|
with TabPane("Abstract"):
|
|
117
117
|
yield Label(self.work_report.work.abstract, classes="abstract")
|
|
@@ -27,13 +27,19 @@ class AuthorResultWidget(ResultWidget):
|
|
|
27
27
|
|
|
28
28
|
def compose(self) -> ComposeResult:
|
|
29
29
|
"""Compose Author result widget."""
|
|
30
|
+
orcid_link = self.author_result.external_id
|
|
31
|
+
|
|
30
32
|
yield Button(label=self.author_result.display_name)
|
|
31
33
|
with Vertical(classes="vertical-content"):
|
|
32
34
|
# Main info
|
|
33
35
|
with Horizontal(classes="main-info-container"):
|
|
34
36
|
yield Label(f"[bold]Cited by count:[/bold] {self.author_result.cited_by_count}", classes="cited-by-count")
|
|
35
37
|
yield Label(f"[bold]Works count:[/bold] {self.author_result.works_count}", classes="works-count")
|
|
36
|
-
|
|
38
|
+
|
|
39
|
+
if orcid_link:
|
|
40
|
+
yield Label(f"""[@click=app.open_link('{quote(str(orcid_link))}')]ORCID[/]""", classes="external-id")
|
|
41
|
+
else:
|
|
42
|
+
yield Label(f"""[@click=app.open_link('{quote(str(self.author_result.id))}')]OpenAlexID[/]""", classes="external-id")
|
|
37
43
|
|
|
38
44
|
# Author hint
|
|
39
45
|
yield Label(self.author_result.hint or "", classes="text-hint")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pub-analyzer
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: A text user interface, written in python, which automates the generation of scientific production reports using OpenAlex
|
|
5
5
|
Home-page: https://github.com/alejandrgaspar/pub-analyzer
|
|
6
6
|
License: MIT
|
|
@@ -21,10 +21,11 @@ Classifier: Programming Language :: Python :: 3
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
25
|
Classifier: Typing :: Typed
|
|
25
|
-
Requires-Dist: httpx (==0.27.
|
|
26
|
+
Requires-Dist: httpx (==0.27.2)
|
|
26
27
|
Requires-Dist: jinja2 (==3.1.4)
|
|
27
|
-
Requires-Dist: pydantic (==2.
|
|
28
|
+
Requires-Dist: pydantic (==2.9.2)
|
|
28
29
|
Requires-Dist: textual (==0.70.0)
|
|
29
30
|
Requires-Dist: typst (==0.11.1)
|
|
30
31
|
Project-URL: Documentation, https://pub-analyzer.com/
|
|
@@ -5,7 +5,7 @@ pub_analyzer/css/checkbox.tcss,sha256=FblyIHns-r1K0ikOnSJtoTMz57C6iDEcscdFAsJ7s4
|
|
|
5
5
|
pub_analyzer/css/collapsible.tcss,sha256=Rh-L5PcIMhnZ7RhY1udd_BcYC1mfCMew2m6o6ty3juE,605
|
|
6
6
|
pub_analyzer/css/datatable.tcss,sha256=JgdMUPc4fYmZlXi_FxbuD88pegK6Pi4FgDHIfA_TKxo,994
|
|
7
7
|
pub_analyzer/css/main.tcss,sha256=rtHLKCVZaBuJ83dAg840cetzVtEy8Rjbw1UKxgafeE8,1027
|
|
8
|
-
pub_analyzer/css/report.tcss,sha256=
|
|
8
|
+
pub_analyzer/css/report.tcss,sha256=H4lHe3yepGLRlDtQkXAx_ONCwCupacglt6j3KWQOf2Q,2027
|
|
9
9
|
pub_analyzer/css/search.tcss,sha256=rovbWjp4pYfCF_OyAC_QrV_0WdMUlsYoQ3vbs9pGw7g,1326
|
|
10
10
|
pub_analyzer/css/summary.tcss,sha256=i4ixICwoQFj2BToW9NVmJGUIYk5upbukbTCnDgT40ds,1350
|
|
11
11
|
pub_analyzer/css/tabs.tcss,sha256=dS7y6ZZmo1Vw7Wqpx66-O-oE7zeqPE9reWqIhQ1KcZs,311
|
|
@@ -13,42 +13,42 @@ pub_analyzer/css/tree.tcss,sha256=5BSabX9ZmRL3VTz0Gya2RRJnWrwdIF9cTf6dXj2R4kE,81
|
|
|
13
13
|
pub_analyzer/internal/__init__.py,sha256=9aqrBJDedUiBO5kEO81kSAuPbOSFoaDZZK8w5NydPhs,22
|
|
14
14
|
pub_analyzer/internal/identifier.py,sha256=LDYew25TLuwqJHmLg9iRNTURWynN27ZbTxTVGbuOUD0,2939
|
|
15
15
|
pub_analyzer/internal/render.py,sha256=gq5dScWs507tne3glJbTQ-PekmALvtcZPB1p_mVAkE0,2144
|
|
16
|
-
pub_analyzer/internal/report.py,sha256=
|
|
17
|
-
pub_analyzer/internal/templates/author/author_summary.typ,sha256=
|
|
18
|
-
pub_analyzer/internal/templates/author/report.typ,sha256=
|
|
19
|
-
pub_analyzer/internal/templates/author/sources.typ,sha256=
|
|
20
|
-
pub_analyzer/internal/templates/author/works.typ,sha256=
|
|
21
|
-
pub_analyzer/internal/templates/author/works_extended.typ,sha256=
|
|
22
|
-
pub_analyzer/main.py,sha256=
|
|
16
|
+
pub_analyzer/internal/report.py,sha256=7T4TTe6ASQnOku0F9YVOGn3G3HmXnad0CVXvGEYQnSA,16322
|
|
17
|
+
pub_analyzer/internal/templates/author/author_summary.typ,sha256=nIECVqTcW_ZjHX6G8Jets_Wrg8Z1Tmm7Kz5RLD46wlQ,2836
|
|
18
|
+
pub_analyzer/internal/templates/author/report.typ,sha256=rlAa5hf2Ms6V3pnlVsu3aosmCwtGq3qtRjbPY9qczNA,2614
|
|
19
|
+
pub_analyzer/internal/templates/author/sources.typ,sha256=lQUorqZRMNI4v8P4xLvtqWy89-fbjciUturI3HrfBPo,798
|
|
20
|
+
pub_analyzer/internal/templates/author/works.typ,sha256=ZhJ3JhyDc4Z5JE3kLmyFJzfSNZdQi-RVlA57SxDBKIg,5132
|
|
21
|
+
pub_analyzer/internal/templates/author/works_extended.typ,sha256=Eh_1BONK3BArWvlo3MNQJ8kueOpW_N8PlxBTa-Guhkg,3510
|
|
22
|
+
pub_analyzer/main.py,sha256=tvGLwYHW-5hysM9sfaU0Il563p9zYcl6Kege--sP7hk,2427
|
|
23
23
|
pub_analyzer/models/__init__.py,sha256=hvR6m379slQw7gSwnl_OFY21Ytv90mmmOe7bp8vZYkk,59
|
|
24
|
-
pub_analyzer/models/author.py,sha256=
|
|
24
|
+
pub_analyzer/models/author.py,sha256=NvFmvSsmnchz2lo7m69NE3kjLYP0CXICRAolnvcznW8,2118
|
|
25
25
|
pub_analyzer/models/concept.py,sha256=yNvajKWTn6uBalNoJmlobitvbFBOjF80jlZnjKjwDRw,677
|
|
26
|
-
pub_analyzer/models/institution.py,sha256=
|
|
26
|
+
pub_analyzer/models/institution.py,sha256=62T9raQYXlEk0wlAj2OJgBy_LnOF7_LUyX8iGywgpOs,3133
|
|
27
27
|
pub_analyzer/models/report.py,sha256=yma70xpD6I8hv_kk5ylMhylLu60eOpQwNEJa-Dd-XQI,2672
|
|
28
28
|
pub_analyzer/models/source.py,sha256=o3ich4iDYB_PH_cVbrZtVRFVLQlPS3W5ajgBQQGzYqM,2730
|
|
29
29
|
pub_analyzer/models/topic.py,sha256=3MBQV-njnjfmOVvgmFZxy8fFU7sMj5yxUW8EHFAjlD4,1825
|
|
30
|
-
pub_analyzer/models/work.py,sha256=
|
|
30
|
+
pub_analyzer/models/work.py,sha256=XvbOdAdm-W69A0534JTl3tFkkZ0KrOIux8poEnWY5Uw,4154
|
|
31
31
|
pub_analyzer/widgets/__init__.py,sha256=JALs1yGE06XYwjoY_0AG-Wt_pMknI1WEWNYK3atQaEA,18
|
|
32
32
|
pub_analyzer/widgets/author/__init__.py,sha256=oiJibt7YiuGpovOnFIAlC9YwLO-0LN3SDgPWFL-LVPQ,22
|
|
33
33
|
pub_analyzer/widgets/author/cards.py,sha256=JWZxYy4Oen5fToiSBgvfEgmBJlrIVXCWpT-XjkLbxY4,2445
|
|
34
|
-
pub_analyzer/widgets/author/core.py,sha256=
|
|
35
|
-
pub_analyzer/widgets/author/tables.py,sha256=
|
|
34
|
+
pub_analyzer/widgets/author/core.py,sha256=9i3U7jSeyQdAdWAslQUMd0_juw3yy9s4mEgl-nJ1LVg,5068
|
|
35
|
+
pub_analyzer/widgets/author/tables.py,sha256=6ZL8B0X0rCpyu_QoAEH0NVvAtTVbydXte3vQIiDui1k,1807
|
|
36
36
|
pub_analyzer/widgets/body.py,sha256=wN9cMcm1MaRTjuHYt8RWrG8D_ngg5cn-hVllvmzPX_o,972
|
|
37
37
|
pub_analyzer/widgets/common/__init__.py,sha256=Fx5Gl17Rd_wueZjNElBtI1kCn-4DMSbC3lEA8u7PSto,287
|
|
38
38
|
pub_analyzer/widgets/common/card.py,sha256=GGSaeuZt6AqY7kAvcVnWNMrhNPzr7do66YRQOYNSYvU,595
|
|
39
39
|
pub_analyzer/widgets/common/filesystem.py,sha256=i0S3D6JJzPkF1Sqm83SSQlmYFKRf82SnoFgKVE6BdYI,6460
|
|
40
|
-
pub_analyzer/widgets/common/filters.py,sha256=
|
|
40
|
+
pub_analyzer/widgets/common/filters.py,sha256=bdtWaxahbFksaZZf6l0Yhgi9opH_RygFHXQV4_CYYj0,3372
|
|
41
41
|
pub_analyzer/widgets/common/input.py,sha256=tK_UCtLDGHlI_NKpKjGkVu4gWiwMAIHixT9Im--Un4c,2649
|
|
42
42
|
pub_analyzer/widgets/common/modal.py,sha256=otLQZotdTRTlSeTBknIxqRyduVY6lRZ5yW5u20SLcwI,882
|
|
43
43
|
pub_analyzer/widgets/common/selector.py,sha256=Jh5bsn-zYmHGfEE3eO9XL6BsgKpLMGfg8FJur4gQmH0,1493
|
|
44
44
|
pub_analyzer/widgets/common/summary.py,sha256=Qj-FRfAVgJmCaVUJI-jQrHX2sGKHTP2b75KukuJWlog,165
|
|
45
45
|
pub_analyzer/widgets/institution/__init__.py,sha256=T_WDTDistaaq2obl1Cy_wZI5nTBiJNUnB-_OwBOLFTE,27
|
|
46
46
|
pub_analyzer/widgets/institution/cards.py,sha256=OgLWP8M0xENa5NnY9NtmwjdqOwZJUN77fXSHFNT3BYU,2862
|
|
47
|
-
pub_analyzer/widgets/institution/core.py,sha256=
|
|
47
|
+
pub_analyzer/widgets/institution/core.py,sha256=6Tb1xQFJERswE1ra2FQFeZcQrb8zSsg-UHfPjWYUOWY,5331
|
|
48
48
|
pub_analyzer/widgets/institution/tables.py,sha256=tXjrop9HGSkZGjYIOGQEOKVoyoeIGPd-8oSh08iuTRw,838
|
|
49
49
|
pub_analyzer/widgets/report/__init__.py,sha256=oolRVss3JKaQHaQVDncjtxbLINRJ5Rd1ulW1uk7MLhc,54
|
|
50
|
-
pub_analyzer/widgets/report/author.py,sha256=
|
|
51
|
-
pub_analyzer/widgets/report/cards.py,sha256=
|
|
50
|
+
pub_analyzer/widgets/report/author.py,sha256=IEfRDfsA8jcmFwQQk1O-iuh8MKr4DbzBPpjoE8xECZA,1459
|
|
51
|
+
pub_analyzer/widgets/report/cards.py,sha256=2jf9cpfzVFZO0I9b29bkNaVhENMnfL26etEpUG-NMk0,4854
|
|
52
52
|
pub_analyzer/widgets/report/concept.py,sha256=xiGXy_RXO_XmdqnlePkOozYPmQrsDdqKPMRXHsZbDP0,1485
|
|
53
53
|
pub_analyzer/widgets/report/core.py,sha256=V4fwBHnSqkUzC1da8sGYqzsPKQRofA7GqpEhbEXsk4s,11436
|
|
54
54
|
pub_analyzer/widgets/report/export.py,sha256=EQzF5fMZgHtLv3f5hITDgf9WW2XytRX_foeLWwcIHkM,4762
|
|
@@ -57,13 +57,13 @@ pub_analyzer/widgets/report/institution.py,sha256=PDPE9fK18l9kKKch5sJrbnHHDss0kJ
|
|
|
57
57
|
pub_analyzer/widgets/report/locations.py,sha256=s6O5v_jX_oPsKOf2fEujtDxLHQRVsqrIcgN4rZkRKkg,2892
|
|
58
58
|
pub_analyzer/widgets/report/source.py,sha256=WJhJc0_sZOcAtkmh9-VjbgugoArZgxKoXlITqVaBYK0,3045
|
|
59
59
|
pub_analyzer/widgets/report/topic.py,sha256=SI3STTBFlpR-VJcsNhJyu6vc9uyytU_ASKuWXb-qr60,1969
|
|
60
|
-
pub_analyzer/widgets/report/work.py,sha256=
|
|
60
|
+
pub_analyzer/widgets/report/work.py,sha256=t1Z2oFB5gUIuCdUqp_vkTV9--oUfnOX-yhlCDVayla4,9592
|
|
61
61
|
pub_analyzer/widgets/search/__init__.py,sha256=8C3IQtFkiIL8hlQbhJ_fAHM59-TAoe29wBAM2ptozhw,239
|
|
62
62
|
pub_analyzer/widgets/search/core.py,sha256=4NvowtBcrH1fmob9kuF7v9Tq3Nd99jzB2S7xaD8OYeI,3861
|
|
63
|
-
pub_analyzer/widgets/search/results.py,sha256=
|
|
63
|
+
pub_analyzer/widgets/search/results.py,sha256=veZP5FxWFKKArH6wvxtvdu07BEYGlT7qwbRjDpgXxtU,3931
|
|
64
64
|
pub_analyzer/widgets/sidebar.py,sha256=XlIshlCVW5Bb3MXFPnU9is0qQrUrGdT6xlkKiYNEcAM,2704
|
|
65
|
-
pub_analyzer-0.4.
|
|
66
|
-
pub_analyzer-0.4.
|
|
67
|
-
pub_analyzer-0.4.
|
|
68
|
-
pub_analyzer-0.4.
|
|
69
|
-
pub_analyzer-0.4.
|
|
65
|
+
pub_analyzer-0.4.3.dist-info/LICENSE,sha256=OPopoEowTMKqIea8Kbxk3TKdCQ97YkLvIknjTHE5oCI,1080
|
|
66
|
+
pub_analyzer-0.4.3.dist-info/METADATA,sha256=kmOal3d94yzhnYLF3ZVZHqKEyXgZROwWszNZuEhR5pk,4559
|
|
67
|
+
pub_analyzer-0.4.3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
68
|
+
pub_analyzer-0.4.3.dist-info/entry_points.txt,sha256=mVb_gUNX_-aVWHlNKLjcMAS8YLgNnSq9JLRXVJGIF2c,54
|
|
69
|
+
pub_analyzer-0.4.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|