hinge 0.1.0__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.
Files changed (111) hide show
  1. hinge/__init__.py +3 -0
  2. hinge/config/__init__.py +0 -0
  3. hinge/config/logging_setup.py +63 -0
  4. hinge/config/settings.py +23 -0
  5. hinge/config/types.yaml +42 -0
  6. hinge/dbt/.user.yml +1 -0
  7. hinge/dbt/dbt_project.yml +18 -0
  8. hinge/dbt/macros/apply_time_window.sql +11 -0
  9. hinge/dbt/macros/assert_recipe_supported.sql +59 -0
  10. hinge/dbt/macros/build_incidence.sql +46 -0
  11. hinge/dbt/macros/collapse_path.sql +26 -0
  12. hinge/dbt/macros/filter_bots.sql +23 -0
  13. hinge/dbt/macros/network_edges.sql +39 -0
  14. hinge/dbt/macros/project_bipartite.sql +33 -0
  15. hinge/dbt/macros/slice_edges.sql +41 -0
  16. hinge/dbt/models/hin/hin_accounts.sql +14 -0
  17. hinge/dbt/models/hin/hin_artifacts.sql +23 -0
  18. hinge/dbt/models/hin/hin_capabilities.sql +42 -0
  19. hinge/dbt/models/hin/hin_edges.sql +57 -0
  20. hinge/dbt/models/hin/hin_nodes.sql +44 -0
  21. hinge/dbt/models/hin/hin_repositories.sql +15 -0
  22. hinge/dbt/models/intermediate/int_developer_repo_affiliation.sql +27 -0
  23. hinge/dbt/models/intermediate/int_user_artifact_incidence.sql +21 -0
  24. hinge/dbt/models/networks/artifact_reference.sql +27 -0
  25. hinge/dbt/models/networks/co_commit_user_user.sql +48 -0
  26. hinge/dbt/models/networks/co_edit_file_user_user.sql +48 -0
  27. hinge/dbt/models/networks/co_edit_line_user_user.sql +48 -0
  28. hinge/dbt/models/networks/dev_interaction.sql +98 -0
  29. hinge/dbt/models/networks/developer_repo_affiliation.sql +20 -0
  30. hinge/dbt/models/networks/follow_user_user.sql +27 -0
  31. hinge/dbt/models/networks/fork_repo_repo.sql +27 -0
  32. hinge/dbt/models/networks/issue_co_participation.sql +50 -0
  33. hinge/dbt/models/networks/issue_participation.sql +40 -0
  34. hinge/dbt/models/networks/pr_author_reviewer.sql +66 -0
  35. hinge/dbt/models/networks/pr_participation.sql +40 -0
  36. hinge/dbt/models/networks/pr_reviewer_coreview.sql +48 -0
  37. hinge/dbt/models/networks/repo_shared_contributors.sql +41 -0
  38. hinge/dbt/models/networks/star_user_repo.sql +27 -0
  39. hinge/dbt/models/networks/user_mention_user.sql +60 -0
  40. hinge/dbt/models/networks/watch_user_repo.sql +27 -0
  41. hinge/dbt/models/sources/sources.yml +63 -0
  42. hinge/dbt/models/staging/stg_accounts.sql +4 -0
  43. hinge/dbt/models/staging/stg_adapter_manifest.sql +4 -0
  44. hinge/dbt/models/staging/stg_artifacts.sql +4 -0
  45. hinge/dbt/models/staging/stg_relations.sql +4 -0
  46. hinge/dbt/models/staging/stg_repositories.sql +4 -0
  47. hinge/dbt/profiles.yml +9 -0
  48. hinge/dbt/seeds/ref_recipe_requirements.csv +35 -0
  49. hinge/frontends/__init__.py +0 -0
  50. hinge/frontends/cli/__init__.py +3 -0
  51. hinge/frontends/cli/app.py +189 -0
  52. hinge/frontends/lib.py +148 -0
  53. hinge/frontends/tui/__init__.py +7 -0
  54. hinge/frontends/web/__init__.py +6 -0
  55. hinge/kernel/__init__.py +0 -0
  56. hinge/kernel/projection/__init__.py +9 -0
  57. hinge/kernel/projection/projected_graph.py +32 -0
  58. hinge/kernel/projection/projection_spec.py +35 -0
  59. hinge/kernel/protocols/__init__.py +24 -0
  60. hinge/kernel/protocols/dataset_view.py +30 -0
  61. hinge/kernel/protocols/exporter_stage.py +24 -0
  62. hinge/kernel/protocols/projection_stage.py +36 -0
  63. hinge/kernel/protocols/reader_stage.py +52 -0
  64. hinge/kernel/protocols/store_stage.py +71 -0
  65. hinge/kernel/registry.py +71 -0
  66. hinge/kernel/runner.py +236 -0
  67. hinge/kernel/schema/__init__.py +12 -0
  68. hinge/kernel/schema/hin_schema.py +38 -0
  69. hinge/kernel/schema/schema_violation.py +21 -0
  70. hinge/kernel/schema/typed_edge.py +20 -0
  71. hinge/kernel/schema/typed_node.py +17 -0
  72. hinge/stages/__init__.py +0 -0
  73. hinge/stages/exporters/__init__.py +0 -0
  74. hinge/stages/exporters/csv_exporter.py +77 -0
  75. hinge/stages/exporters/dot_exporter.py +13 -0
  76. hinge/stages/exporters/gexf_exporter.py +13 -0
  77. hinge/stages/exporters/gml_exporter.py +79 -0
  78. hinge/stages/exporters/graphml_exporter.py +13 -0
  79. hinge/stages/exporters/jsonld_exporter.py +13 -0
  80. hinge/stages/exporters/networkx_exporter.py +57 -0
  81. hinge/stages/exporters/parquet_exporter.py +13 -0
  82. hinge/stages/projection/__init__.py +0 -0
  83. hinge/stages/projection/dbt_projection.py +342 -0
  84. hinge/stages/projection/specs/__init__.py +0 -0
  85. hinge/stages/projection/specs/artifact_reference.py +14 -0
  86. hinge/stages/projection/specs/co_commit_user_user.py +14 -0
  87. hinge/stages/projection/specs/co_edit_file_user_user.py +14 -0
  88. hinge/stages/projection/specs/co_edit_line_user_user.py +14 -0
  89. hinge/stages/projection/specs/dev_interaction.py +26 -0
  90. hinge/stages/projection/specs/developer_repo_affiliation.py +15 -0
  91. hinge/stages/projection/specs/follow_user_user.py +14 -0
  92. hinge/stages/projection/specs/fork_repo_repo.py +14 -0
  93. hinge/stages/projection/specs/issue_co_participation.py +14 -0
  94. hinge/stages/projection/specs/issue_participation.py +14 -0
  95. hinge/stages/projection/specs/pr_author_reviewer.py +14 -0
  96. hinge/stages/projection/specs/pr_participation.py +14 -0
  97. hinge/stages/projection/specs/pr_reviewer_coreview.py +14 -0
  98. hinge/stages/projection/specs/repo_shared_contributors.py +14 -0
  99. hinge/stages/projection/specs/star_user_repo.py +15 -0
  100. hinge/stages/projection/specs/user_mention_user.py +14 -0
  101. hinge/stages/projection/specs/watch_user_repo.py +14 -0
  102. hinge/stages/readers/__init__.py +0 -0
  103. hinge/stages/readers/_numfocus_contract_adapter.py +314 -0
  104. hinge/stages/readers/numfocus_reader.py +69 -0
  105. hinge/stages/store/__init__.py +0 -0
  106. hinge/stages/store/duckdb_store.py +466 -0
  107. hinge-0.1.0.dist-info/METADATA +309 -0
  108. hinge-0.1.0.dist-info/RECORD +111 -0
  109. hinge-0.1.0.dist-info/WHEEL +4 -0
  110. hinge-0.1.0.dist-info/entry_points.txt +35 -0
  111. hinge-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,21 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ build_incidence(
4
+ left_type='user',
5
+ right_type='artifact',
6
+ edge_types=[
7
+ 'authored',
8
+ 'committed',
9
+ 'coauthored',
10
+ 'opened',
11
+ 'reviewed',
12
+ 'merged',
13
+ 'closed',
14
+ 'reopened',
15
+ 'commented_on',
16
+ 'review_commented_on',
17
+ 'pushed',
18
+ 'touched'
19
+ ],
20
+ weight_mode='event_count'
21
+ ) }}
@@ -0,0 +1,27 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('artifact_reference') }}
4
+
5
+ WITH reference_edges AS (
6
+ {{ slice_edges(
7
+ edge_types=['references', 'closes', 'fixes', 'duplicates', 'relates_to'],
8
+ source_types=['artifact'],
9
+ target_types=['artifact']
10
+ ) }}
11
+ )
12
+
13
+ {{ network_edges(
14
+ relation='reference_edges',
15
+ recipe_name='artifact_reference',
16
+ source_node_id='source_node_id',
17
+ source_node_type="'artifact'",
18
+ target_node_id='target_node_id',
19
+ target_node_type="'artifact'",
20
+ edge_type='edge_type',
21
+ directed='true',
22
+ weight='coalesce(weight, 1.0)',
23
+ weight_kind="'binary'",
24
+ first_seen_at='occurred_at',
25
+ last_seen_at='occurred_at',
26
+ properties="to_json({'source_record_id': source_record_id, 'reference_type': edge_type})"
27
+ ) }}
@@ -0,0 +1,48 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('co_commit_user_user') }}
4
+
5
+ WITH commit_events AS (
6
+ SELECT *
7
+ FROM {{ ref('int_user_artifact_incidence') }}
8
+ WHERE right_node_subtype = 'commit'
9
+ AND role IN ('authored', 'committed', 'coauthored')
10
+ ),
11
+
12
+ user_commit AS (
13
+ SELECT
14
+ left_node_id AS user_node_id,
15
+ right_node_id AS commit_node_id,
16
+ min(occurred_at) AS first_seen_at,
17
+ max(occurred_at) AS last_seen_at,
18
+ sum(weight)::DOUBLE AS weight
19
+ FROM commit_events
20
+ GROUP BY left_node_id, right_node_id
21
+ ),
22
+
23
+ co_commits AS (
24
+ {{ project_bipartite(
25
+ incidence_relation='user_commit',
26
+ left_col='user_node_id',
27
+ right_col='commit_node_id',
28
+ directed=false,
29
+ weight_mode='shared_count'
30
+ ) }}
31
+ )
32
+
33
+ {{ network_edges(
34
+ relation='co_commits',
35
+ recipe_name='co_commit_user_user',
36
+ source_node_id='source_node_id',
37
+ source_node_type="'user'",
38
+ target_node_id='target_node_id',
39
+ target_node_type="'user'",
40
+ edge_type="'co_committed'",
41
+ directed='false',
42
+ weight='weight',
43
+ weight_kind='weight_kind',
44
+ n_contexts='n_contexts',
45
+ first_seen_at='first_seen_at',
46
+ last_seen_at='last_seen_at',
47
+ properties="to_json({'shared_commits': n_contexts, 'commits': context_node_ids})"
48
+ ) }}
@@ -0,0 +1,48 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('co_edit_file_user_user') }}
4
+
5
+ WITH file_touch_events AS (
6
+ SELECT *
7
+ FROM {{ ref('int_user_artifact_incidence') }}
8
+ WHERE right_node_subtype = 'file'
9
+ AND role = 'touched'
10
+ ),
11
+
12
+ user_file AS (
13
+ SELECT
14
+ left_node_id AS user_node_id,
15
+ right_node_id AS file_node_id,
16
+ min(occurred_at) AS first_seen_at,
17
+ max(occurred_at) AS last_seen_at,
18
+ sum(weight)::DOUBLE AS weight
19
+ FROM file_touch_events
20
+ GROUP BY left_node_id, right_node_id
21
+ ),
22
+
23
+ co_edits AS (
24
+ {{ project_bipartite(
25
+ incidence_relation='user_file',
26
+ left_col='user_node_id',
27
+ right_col='file_node_id',
28
+ directed=false,
29
+ weight_mode='shared_count'
30
+ ) }}
31
+ )
32
+
33
+ {{ network_edges(
34
+ relation='co_edits',
35
+ recipe_name='co_edit_file_user_user',
36
+ source_node_id='source_node_id',
37
+ source_node_type="'user'",
38
+ target_node_id='target_node_id',
39
+ target_node_type="'user'",
40
+ edge_type="'co_edited_file'",
41
+ directed='false',
42
+ weight='weight',
43
+ weight_kind='weight_kind',
44
+ n_contexts='n_contexts',
45
+ first_seen_at='first_seen_at',
46
+ last_seen_at='last_seen_at',
47
+ properties="to_json({'shared_files': n_contexts, 'files': context_node_ids})"
48
+ ) }}
@@ -0,0 +1,48 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('co_edit_line_user_user') }}
4
+
5
+ WITH line_touch_events AS (
6
+ SELECT *
7
+ FROM {{ ref('int_user_artifact_incidence') }}
8
+ WHERE right_node_subtype = 'line_span'
9
+ AND role = 'touched'
10
+ ),
11
+
12
+ user_line AS (
13
+ SELECT
14
+ left_node_id AS user_node_id,
15
+ right_node_id AS line_node_id,
16
+ min(occurred_at) AS first_seen_at,
17
+ max(occurred_at) AS last_seen_at,
18
+ sum(weight)::DOUBLE AS weight
19
+ FROM line_touch_events
20
+ GROUP BY left_node_id, right_node_id
21
+ ),
22
+
23
+ co_edits AS (
24
+ {{ project_bipartite(
25
+ incidence_relation='user_line',
26
+ left_col='user_node_id',
27
+ right_col='line_node_id',
28
+ directed=false,
29
+ weight_mode='shared_count'
30
+ ) }}
31
+ )
32
+
33
+ {{ network_edges(
34
+ relation='co_edits',
35
+ recipe_name='co_edit_line_user_user',
36
+ source_node_id='source_node_id',
37
+ source_node_type="'user'",
38
+ target_node_id='target_node_id',
39
+ target_node_type="'user'",
40
+ edge_type="'co_edited_line'",
41
+ directed='false',
42
+ weight='weight',
43
+ weight_kind='weight_kind',
44
+ n_contexts='n_contexts',
45
+ first_seen_at='first_seen_at',
46
+ last_seen_at='last_seen_at',
47
+ properties="to_json({'shared_lines': n_contexts, 'line_spans': context_node_ids})"
48
+ ) }}
@@ -0,0 +1,98 @@
1
+ -- ============================================================================
2
+ -- dev_interaction.sql — developer collaboration network
3
+ --
4
+ -- READ THIS BEFORE WRITING A NEW PROJECTION. Every .sql file in this folder
5
+ -- must satisfy the contract documented in the header below.
6
+ -- ============================================================================
7
+ --
8
+ -- WHAT THIS PROJECTION DOES
9
+ -- Builds an undirected user-to-user "collaborator" graph. Two developers
10
+ -- are collaborators if they both made code contributions to the same
11
+ -- repository. A contribution is any of: opening a PR or issue, reviewing,
12
+ -- merging, commenting on a PR or issue, or pushing commits.
13
+ --
14
+ -- "Shared a repo" is derived from the HIN structure using two hops:
15
+ -- user → <contribution edge> → artifact ← contains ← repo
16
+ -- The self-join on repo then finds all pairs of contributors per repo.
17
+ --
18
+ -- Starring, forking, and wiki edits are intentionally excluded — they
19
+ -- do not constitute active development collaboration.
20
+ --
21
+ -- ─── INPUT CONTRACT ─────────────────────────────────────────────────────────
22
+ -- Every projection reads from exactly two dbt sources:
23
+ --
24
+ -- {{ ref('hin_nodes') }} canonical HIN node model
25
+ -- {{ ref('hin_edges') }} canonical HIN edge model
26
+ --
27
+ -- The store (DuckDBStore) rewrites these views before each projection run
28
+ -- to point at the requested dataset_id. Do NOT touch any other table.
29
+ -- If you need helper logic, put it in a CTE — do not create intermediate
30
+ -- dbt models unless the projection genuinely needs to be split.
31
+ --
32
+ -- ─── OUTPUT CONTRACT ────────────────────────────────────────────────────────
33
+ -- Every projection model should use the network_edges macro. It emits the
34
+ -- standard cookbook shape:
35
+ --
36
+ -- recipe_name, recipe_version,
37
+ -- source_node_id, source_node_type, target_node_id, target_node_type,
38
+ -- directed, edge_type,
39
+ -- weight, weight_kind, n_contexts, n_events,
40
+ -- first_seen_at, last_seen_at, time_bin, bot_policy, properties
41
+ --
42
+ -- DbtProjection converts this richer table into TypedEdge objects for the
43
+ -- current exporter API, merging standard fields and properties into attrs.
44
+ --
45
+ -- The DbtProjection stage discovers the result table by its dbt model
46
+ -- name (this file's stem, `dev_interaction`). dbt's default
47
+ -- materialisation is `table`, set globally in dbt_project.yml.
48
+ --
49
+ -- ─── HOW TO REGISTER THIS FILE ──────────────────────────────────────────────
50
+ -- 1. Save the .sql file in hinge/dbt/models/networks/<name>.sql.
51
+ -- 2. Add a sibling Python module under
52
+ -- hinge/stages/projection/specs/<name>.py exposing a `SPEC` constant
53
+ -- (see specs/dev_interaction.py for the template).
54
+ -- 3. Register the entry-point in pyproject.toml under
55
+ -- [project.entry-points."hinge.projection_specs"].
56
+ -- 4. Write a test in tests/stages/projection/test_<name>.py against a
57
+ -- seeded :memory: DuckDB fixture.
58
+ --
59
+ -- ─── PARAMETERS ─────────────────────────────────────────────────────────────
60
+ -- Projections can accept run-time parameters via dbt vars (passed by
61
+ -- DbtProjection from the ProjectionParams object). Read them with
62
+ -- {{ var('name', default) }}.
63
+ -- This template ignores all vars — keep it that way until a real use case
64
+ -- appears.
65
+ -- ============================================================================
66
+
67
+ {{ assert_recipe_supported('dev_interaction') }}
68
+
69
+ WITH
70
+
71
+ -- Collaborator pairs: users who share at least one active development repo.
72
+ -- This is the generic bipartite projection pattern: user -> repo <- user.
73
+ collaborators AS (
74
+ {{ project_bipartite(
75
+ incidence_relation=ref('int_developer_repo_affiliation'),
76
+ left_col='user_node_id',
77
+ right_col='repo_node_id',
78
+ directed=false,
79
+ weight_mode='shared_count'
80
+ ) }}
81
+ )
82
+
83
+ {{ network_edges(
84
+ relation='collaborators',
85
+ recipe_name='dev_interaction',
86
+ source_node_id='source_node_id',
87
+ source_node_type="'user'",
88
+ target_node_id='target_node_id',
89
+ target_node_type="'user'",
90
+ edge_type="'collaborates_with'",
91
+ directed='false',
92
+ weight='weight',
93
+ weight_kind='weight_kind',
94
+ n_contexts='n_contexts',
95
+ first_seen_at='first_seen_at',
96
+ last_seen_at='last_seen_at',
97
+ properties="to_json({'shared_repos': n_contexts, 'repos': context_node_ids})"
98
+ ) }}
@@ -0,0 +1,20 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('developer_repo_affiliation') }}
4
+
5
+ {{ network_edges(
6
+ relation=ref('int_developer_repo_affiliation'),
7
+ recipe_name='developer_repo_affiliation',
8
+ source_node_id='user_node_id',
9
+ source_node_type="'user'",
10
+ target_node_id='repo_node_id',
11
+ target_node_type="'repo'",
12
+ edge_type="'affiliated_with'",
13
+ directed='true',
14
+ weight='weight',
15
+ weight_kind="'event_count'",
16
+ n_events='n_events',
17
+ first_seen_at='first_seen_at',
18
+ last_seen_at='last_seen_at',
19
+ properties="to_json({'roles': roles})"
20
+ ) }}
@@ -0,0 +1,27 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('follow_user_user') }}
4
+
5
+ WITH follow_edges AS (
6
+ {{ slice_edges(
7
+ edge_types=['follows'],
8
+ source_types=['user'],
9
+ target_types=['user']
10
+ ) }}
11
+ )
12
+
13
+ {{ network_edges(
14
+ relation='follow_edges',
15
+ recipe_name='follow_user_user',
16
+ source_node_id='source_node_id',
17
+ source_node_type="'user'",
18
+ target_node_id='target_node_id',
19
+ target_node_type="'user'",
20
+ edge_type="'follows'",
21
+ directed='true',
22
+ weight='coalesce(weight, 1.0)',
23
+ weight_kind="'binary'",
24
+ first_seen_at='occurred_at',
25
+ last_seen_at='occurred_at',
26
+ properties="to_json({'source_record_id': source_record_id})"
27
+ ) }}
@@ -0,0 +1,27 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('fork_repo_repo') }}
4
+
5
+ WITH fork_edges AS (
6
+ {{ slice_edges(
7
+ edge_types=['fork_of'],
8
+ source_types=['repo'],
9
+ target_types=['repo']
10
+ ) }}
11
+ )
12
+
13
+ {{ network_edges(
14
+ relation='fork_edges',
15
+ recipe_name='fork_repo_repo',
16
+ source_node_id='source_node_id',
17
+ source_node_type="'repo'",
18
+ target_node_id='target_node_id',
19
+ target_node_type="'repo'",
20
+ edge_type="'fork_of'",
21
+ directed='true',
22
+ weight='coalesce(weight, 1.0)',
23
+ weight_kind="'binary'",
24
+ first_seen_at='occurred_at',
25
+ last_seen_at='occurred_at',
26
+ properties="to_json({'source_record_id': source_record_id})"
27
+ ) }}
@@ -0,0 +1,50 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('issue_co_participation') }}
4
+
5
+ WITH issue_events AS (
6
+ SELECT *
7
+ FROM {{ ref('int_user_artifact_incidence') }}
8
+ WHERE right_node_subtype = 'issue'
9
+ AND role IN ('opened', 'commented_on', 'closed')
10
+ ),
11
+
12
+ issue_participation AS (
13
+ SELECT
14
+ left_node_id AS user_node_id,
15
+ right_node_id AS issue_node_id,
16
+ min(occurred_at) AS first_seen_at,
17
+ max(occurred_at) AS last_seen_at,
18
+ sum(weight)::DOUBLE AS weight,
19
+ count(DISTINCT evidence_edge_id) AS n_events,
20
+ list_distinct(list(role)) AS roles
21
+ FROM issue_events
22
+ GROUP BY left_node_id, right_node_id
23
+ ),
24
+
25
+ co_participation AS (
26
+ {{ project_bipartite(
27
+ incidence_relation='issue_participation',
28
+ left_col='user_node_id',
29
+ right_col='issue_node_id',
30
+ directed=false,
31
+ weight_mode='shared_count'
32
+ ) }}
33
+ )
34
+
35
+ {{ network_edges(
36
+ relation='co_participation',
37
+ recipe_name='issue_co_participation',
38
+ source_node_id='source_node_id',
39
+ source_node_type="'user'",
40
+ target_node_id='target_node_id',
41
+ target_node_type="'user'",
42
+ edge_type="'co_participates_issue'",
43
+ directed='false',
44
+ weight='weight',
45
+ weight_kind='weight_kind',
46
+ n_contexts='n_contexts',
47
+ first_seen_at='first_seen_at',
48
+ last_seen_at='last_seen_at',
49
+ properties="to_json({'shared_issues': n_contexts, 'issues': context_node_ids})"
50
+ ) }}
@@ -0,0 +1,40 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('issue_participation') }}
4
+
5
+ WITH issue_events AS (
6
+ SELECT *
7
+ FROM {{ ref('int_user_artifact_incidence') }}
8
+ WHERE right_node_subtype = 'issue'
9
+ AND role IN ('opened', 'commented_on', 'closed')
10
+ ),
11
+
12
+ participation AS (
13
+ SELECT
14
+ left_node_id AS user_node_id,
15
+ right_node_id AS issue_node_id,
16
+ min(occurred_at) AS first_seen_at,
17
+ max(occurred_at) AS last_seen_at,
18
+ sum(weight)::DOUBLE AS weight,
19
+ count(DISTINCT evidence_edge_id) AS n_events,
20
+ list_distinct(list(role)) AS roles
21
+ FROM issue_events
22
+ GROUP BY left_node_id, right_node_id
23
+ )
24
+
25
+ {{ network_edges(
26
+ relation='participation',
27
+ recipe_name='issue_participation',
28
+ source_node_id='user_node_id',
29
+ source_node_type="'user'",
30
+ target_node_id='issue_node_id',
31
+ target_node_type="'artifact'",
32
+ edge_type="'participated_in_issue'",
33
+ directed='true',
34
+ weight='weight',
35
+ weight_kind="'event_count'",
36
+ n_events='n_events',
37
+ first_seen_at='first_seen_at',
38
+ last_seen_at='last_seen_at',
39
+ properties="to_json({'roles': roles})"
40
+ ) }}
@@ -0,0 +1,66 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('pr_author_reviewer') }}
4
+
5
+ WITH pr_events AS (
6
+ SELECT *
7
+ FROM {{ ref('int_user_artifact_incidence') }}
8
+ WHERE right_node_subtype = 'pull_request'
9
+ AND role IN ('opened', 'reviewed')
10
+ ),
11
+
12
+ openers AS (
13
+ SELECT
14
+ left_node_id AS author_node_id,
15
+ right_node_id AS pr_node_id,
16
+ min(occurred_at) AS first_seen_at
17
+ FROM pr_events
18
+ WHERE role = 'opened'
19
+ GROUP BY left_node_id, right_node_id
20
+ ),
21
+
22
+ reviewers AS (
23
+ SELECT
24
+ left_node_id AS reviewer_node_id,
25
+ right_node_id AS pr_node_id,
26
+ min(occurred_at) AS first_seen_at,
27
+ max(occurred_at) AS last_seen_at,
28
+ count(DISTINCT evidence_edge_id) AS n_reviews
29
+ FROM pr_events
30
+ WHERE role = 'reviewed'
31
+ GROUP BY left_node_id, right_node_id
32
+ ),
33
+
34
+ author_reviewer AS (
35
+ SELECT
36
+ openers.author_node_id,
37
+ reviewers.reviewer_node_id,
38
+ count(DISTINCT openers.pr_node_id) AS n_contexts,
39
+ sum(reviewers.n_reviews) AS n_events,
40
+ list_distinct(list(openers.pr_node_id)) AS pull_requests,
41
+ min(least(openers.first_seen_at, reviewers.first_seen_at)) AS first_seen_at,
42
+ max(reviewers.last_seen_at) AS last_seen_at
43
+ FROM openers
44
+ JOIN reviewers
45
+ ON reviewers.pr_node_id = openers.pr_node_id
46
+ AND reviewers.reviewer_node_id != openers.author_node_id
47
+ GROUP BY openers.author_node_id, reviewers.reviewer_node_id
48
+ )
49
+
50
+ {{ network_edges(
51
+ relation='author_reviewer',
52
+ recipe_name='pr_author_reviewer',
53
+ source_node_id='author_node_id',
54
+ source_node_type="'user'",
55
+ target_node_id='reviewer_node_id',
56
+ target_node_type="'user'",
57
+ edge_type="'reviewed_pr_from'",
58
+ directed='true',
59
+ weight='n_contexts',
60
+ weight_kind="'shared_count'",
61
+ n_contexts='n_contexts',
62
+ n_events='n_events',
63
+ first_seen_at='first_seen_at',
64
+ last_seen_at='last_seen_at',
65
+ properties="to_json({'pull_requests': pull_requests})"
66
+ ) }}
@@ -0,0 +1,40 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('pr_participation') }}
4
+
5
+ WITH pr_events AS (
6
+ SELECT *
7
+ FROM {{ ref('int_user_artifact_incidence') }}
8
+ WHERE right_node_subtype = 'pull_request'
9
+ AND role IN ('opened', 'reviewed', 'merged', 'commented_on', 'review_commented_on')
10
+ ),
11
+
12
+ participation AS (
13
+ SELECT
14
+ left_node_id AS user_node_id,
15
+ right_node_id AS pr_node_id,
16
+ min(occurred_at) AS first_seen_at,
17
+ max(occurred_at) AS last_seen_at,
18
+ sum(weight)::DOUBLE AS weight,
19
+ count(DISTINCT evidence_edge_id) AS n_events,
20
+ list_distinct(list(role)) AS roles
21
+ FROM pr_events
22
+ GROUP BY left_node_id, right_node_id
23
+ )
24
+
25
+ {{ network_edges(
26
+ relation='participation',
27
+ recipe_name='pr_participation',
28
+ source_node_id='user_node_id',
29
+ source_node_type="'user'",
30
+ target_node_id='pr_node_id',
31
+ target_node_type="'artifact'",
32
+ edge_type="'participated_in_pr'",
33
+ directed='true',
34
+ weight='weight',
35
+ weight_kind="'event_count'",
36
+ n_events='n_events',
37
+ first_seen_at='first_seen_at',
38
+ last_seen_at='last_seen_at',
39
+ properties="to_json({'roles': roles})"
40
+ ) }}
@@ -0,0 +1,48 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('pr_reviewer_coreview') }}
4
+
5
+ WITH review_events AS (
6
+ SELECT *
7
+ FROM {{ ref('int_user_artifact_incidence') }}
8
+ WHERE right_node_subtype = 'pull_request'
9
+ AND role = 'reviewed'
10
+ ),
11
+
12
+ reviewer_pr AS (
13
+ SELECT
14
+ left_node_id AS reviewer_node_id,
15
+ right_node_id AS pr_node_id,
16
+ min(occurred_at) AS first_seen_at,
17
+ max(occurred_at) AS last_seen_at,
18
+ sum(weight)::DOUBLE AS weight
19
+ FROM review_events
20
+ GROUP BY left_node_id, right_node_id
21
+ ),
22
+
23
+ co_reviews AS (
24
+ {{ project_bipartite(
25
+ incidence_relation='reviewer_pr',
26
+ left_col='reviewer_node_id',
27
+ right_col='pr_node_id',
28
+ directed=false,
29
+ weight_mode='shared_count'
30
+ ) }}
31
+ )
32
+
33
+ {{ network_edges(
34
+ relation='co_reviews',
35
+ recipe_name='pr_reviewer_coreview',
36
+ source_node_id='source_node_id',
37
+ source_node_type="'user'",
38
+ target_node_id='target_node_id',
39
+ target_node_type="'user'",
40
+ edge_type="'co_reviewed_pr'",
41
+ directed='false',
42
+ weight='weight',
43
+ weight_kind='weight_kind',
44
+ n_contexts='n_contexts',
45
+ first_seen_at='first_seen_at',
46
+ last_seen_at='last_seen_at',
47
+ properties="to_json({'shared_pull_requests': n_contexts, 'pull_requests': context_node_ids})"
48
+ ) }}
@@ -0,0 +1,41 @@
1
+ {{ config(materialized='table') }}
2
+
3
+ {{ assert_recipe_supported('repo_shared_contributors') }}
4
+
5
+ WITH contributor_repo AS (
6
+ SELECT
7
+ repo_node_id,
8
+ user_node_id,
9
+ min(first_seen_at) AS first_seen_at,
10
+ max(last_seen_at) AS last_seen_at,
11
+ sum(weight)::DOUBLE AS weight
12
+ FROM {{ ref('int_developer_repo_affiliation') }}
13
+ GROUP BY repo_node_id, user_node_id
14
+ ),
15
+
16
+ shared AS (
17
+ {{ project_bipartite(
18
+ incidence_relation='contributor_repo',
19
+ left_col='repo_node_id',
20
+ right_col='user_node_id',
21
+ directed=false,
22
+ weight_mode='shared_count'
23
+ ) }}
24
+ )
25
+
26
+ {{ network_edges(
27
+ relation='shared',
28
+ recipe_name='repo_shared_contributors',
29
+ source_node_id='source_node_id',
30
+ source_node_type="'repo'",
31
+ target_node_id='target_node_id',
32
+ target_node_type="'repo'",
33
+ edge_type="'shared_contributors'",
34
+ directed='false',
35
+ weight='weight',
36
+ weight_kind='weight_kind',
37
+ n_contexts='n_contexts',
38
+ first_seen_at='first_seen_at',
39
+ last_seen_at='last_seen_at',
40
+ properties="to_json({'shared_contributors': n_contexts, 'contributors': context_node_ids})"
41
+ ) }}