ledger: extract albums->photos join table (100 albums x 50 photos)

This commit is contained in:
ledger committed 2026-10-31 16:29:18 +00:00
1 parent 0fb2caee90
commit 974a94eabf
6 files changed
+930

No files matched your search

@@ -0,0 +1,101 @@
id,userId,title,photo_count
1,1,quidem molestiae enim,50
2,1,sunt qui excepturi placeat culpa,50
3,1,omnis laborum odio,50
4,1,non esse culpa molestiae omnis sed optio,50
5,1,eaque aut omnis a,50
6,1,natus impedit quibusdam illo est,50
7,1,quibusdam autem aliquid et et quia,50
8,1,qui fuga est a eum,50
9,1,saepe unde necessitatibus rem,50
10,1,distinctio laborum qui,50
11,2,quam nostrum impedit mollitia quod et dolor,50
12,2,consequatur autem doloribus natus consectetur,50
13,2,ab rerum non rerum consequatur ut ea unde,50
14,2,ducimus molestias eos animi atque nihil,50
15,2,ut pariatur rerum ipsum natus repellendus praesentium,50
16,2,voluptatem aut maxime inventore autem magnam atque repellat,50
17,2,aut minima voluptatem ut velit,50
18,2,nesciunt quia et doloremque,50
19,2,velit pariatur quaerat similique libero omnis quia,50
20,2,voluptas rerum iure ut enim,50
21,3,repudiandae voluptatem optio est consequatur rem in temporibus et,50
22,3,et rem non provident vel ut,50
23,3,incidunt quisquam hic adipisci sequi,50
24,3,dolores ut et facere placeat,50
25,3,vero maxime id possimus sunt neque et consequatur,50
26,3,quibusdam saepe ipsa vel harum,50
27,3,id non nostrum expedita,50
28,3,omnis neque exercitationem sed dolor atque maxime aut cum,50
29,3,inventore ut quasi magnam itaque est fugit,50
30,3,tempora assumenda et similique odit distinctio error,50
31,4,adipisci laborum fuga laboriosam,50
32,4,reiciendis dolores a ut qui debitis non quo labore,50
33,4,iste eos nostrum,50
34,4,cumque voluptatibus rerum architecto blanditiis,50
35,4,et impedit nisi quae magni necessitatibus sed aut pariatur,50
36,4,nihil cupiditate voluptate neque,50
37,4,est placeat dicta ut nisi rerum iste,50
38,4,unde a sequi id,50
39,4,ratione porro illum labore eum aperiam sed,50
40,4,voluptas neque et sint aut quo odit,50
41,5,ea voluptates maiores eos accusantium officiis tempore mollitia consequatur,50
42,5,tenetur explicabo ea,50
43,5,aperiam doloremque nihil,50
44,5,sapiente cum numquam officia consequatur vel natus quos suscipit,50
45,5,tenetur quos ea unde est enim corrupti qui,50
46,5,molestiae voluptate non,50
47,5,temporibus molestiae aut,50
48,5,modi consequatur culpa aut quam soluta alias perspiciatis laudantium,50
49,5,ut aut vero repudiandae voluptas ullam voluptas at consequatur,50
50,5,sed qui sed quas sit ducimus dolor,50
51,6,odit laboriosam sint quia cupiditate animi quis,50
52,6,necessitatibus quas et sunt at voluptatem,50
53,6,est vel sequi voluptatem nemo quam molestiae modi enim,50
54,6,aut non illo amet perferendis,50
55,6,qui culpa itaque omnis in nesciunt architecto error,50
56,6,omnis qui maiores tempora officiis omnis rerum sed repellat,50
57,6,libero excepturi voluptatem est architecto quae voluptatum officia tempora,50
58,6,nulla illo consequatur aspernatur veritatis aut error delectus et,50
59,6,eligendi similique provident nihil,50
60,6,omnis mollitia sunt aliquid eum consequatur fugit minus laudantium,50
61,7,delectus iusto et,50
62,7,eos ea non recusandae iste ut quasi,50
63,7,velit est quam,50
64,7,autem voluptatem amet iure quae,50
65,7,voluptates delectus iure iste qui,50
66,7,velit sed quia dolor dolores delectus,50
67,7,ad voluptas nostrum et nihil,50
68,7,qui quasi nihil aut voluptatum sit dolore minima,50
69,7,qui aut est,50
70,7,et deleniti unde,50
71,8,et vel corporis,50
72,8,unde exercitationem ut,50
73,8,quos omnis officia,50
74,8,quia est eius vitae dolor,50
75,8,aut quia expedita non,50
76,8,dolorem magnam facere itaque ut reprehenderit tenetur corrupti,50
77,8,cupiditate sapiente maiores iusto ducimus cum excepturi veritatis quia,50
78,8,est minima eius possimus ea ratione velit et,50
79,8,ipsa quae voluptas natus ut suscipit soluta quia quidem,50
80,8,id nihil reprehenderit,50
81,9,quibusdam sapiente et,50
82,9,recusandae consequatur vel amet unde,50
83,9,aperiam odio fugiat,50
84,9,est et at eos expedita,50
85,9,qui voluptatem consequatur aut ab quis temporibus praesentium,50
86,9,eligendi mollitia alias aspernatur vel ut iusto,50
87,9,aut aut architecto,50
88,9,quas perspiciatis optio,50
89,9,sit optio id voluptatem est eum et,50
90,9,est vel dignissimos,50
91,10,repellendus praesentium debitis officiis,50
92,10,incidunt et et eligendi assumenda soluta quia recusandae,50
93,10,nisi qui dolores perspiciatis,50
94,10,quisquam a dolores et earum vitae,50
95,10,consectetur vel rerum qui aperiam modi eos aspernatur ipsa,50
96,10,unde et ut molestiae est molestias voluptatem sint,50
97,10,est quod aut,50
98,10,omnis quia possimus nesciunt deleniti assumenda sed autem,50
99,10,consectetur ut id impedit dolores sit ad ex aut,50
100,10,enim repellat iste,50
1 id userId title photo_count
2 1 1 quidem molestiae enim 50
3 2 1 sunt qui excepturi placeat culpa 50
4 3 1 omnis laborum odio 50
5 4 1 non esse culpa molestiae omnis sed optio 50
6 5 1 eaque aut omnis a 50
7 6 1 natus impedit quibusdam illo est 50
8 7 1 quibusdam autem aliquid et et quia 50
9 8 1 qui fuga est a eum 50
10 9 1 saepe unde necessitatibus rem 50
11 10 1 distinctio laborum qui 50
12 11 2 quam nostrum impedit mollitia quod et dolor 50
13 12 2 consequatur autem doloribus natus consectetur 50
14 13 2 ab rerum non rerum consequatur ut ea unde 50
15 14 2 ducimus molestias eos animi atque nihil 50
16 15 2 ut pariatur rerum ipsum natus repellendus praesentium 50
17 16 2 voluptatem aut maxime inventore autem magnam atque repellat 50
18 17 2 aut minima voluptatem ut velit 50
19 18 2 nesciunt quia et doloremque 50
20 19 2 velit pariatur quaerat similique libero omnis quia 50
21 20 2 voluptas rerum iure ut enim 50
22 21 3 repudiandae voluptatem optio est consequatur rem in temporibus et 50
23 22 3 et rem non provident vel ut 50
24 23 3 incidunt quisquam hic adipisci sequi 50
25 24 3 dolores ut et facere placeat 50
26 25 3 vero maxime id possimus sunt neque et consequatur 50
27 26 3 quibusdam saepe ipsa vel harum 50
28 27 3 id non nostrum expedita 50
29 28 3 omnis neque exercitationem sed dolor atque maxime aut cum 50
30 29 3 inventore ut quasi magnam itaque est fugit 50
31 30 3 tempora assumenda et similique odit distinctio error 50
32 31 4 adipisci laborum fuga laboriosam 50
33 32 4 reiciendis dolores a ut qui debitis non quo labore 50
34 33 4 iste eos nostrum 50
35 34 4 cumque voluptatibus rerum architecto blanditiis 50
36 35 4 et impedit nisi quae magni necessitatibus sed aut pariatur 50
37 36 4 nihil cupiditate voluptate neque 50
38 37 4 est placeat dicta ut nisi rerum iste 50
39 38 4 unde a sequi id 50
40 39 4 ratione porro illum labore eum aperiam sed 50
41 40 4 voluptas neque et sint aut quo odit 50
42 41 5 ea voluptates maiores eos accusantium officiis tempore mollitia consequatur 50
43 42 5 tenetur explicabo ea 50
44 43 5 aperiam doloremque nihil 50
45 44 5 sapiente cum numquam officia consequatur vel natus quos suscipit 50
46 45 5 tenetur quos ea unde est enim corrupti qui 50
47 46 5 molestiae voluptate non 50
48 47 5 temporibus molestiae aut 50
49 48 5 modi consequatur culpa aut quam soluta alias perspiciatis laudantium 50
50 49 5 ut aut vero repudiandae voluptas ullam voluptas at consequatur 50
51 50 5 sed qui sed quas sit ducimus dolor 50
52 51 6 odit laboriosam sint quia cupiditate animi quis 50
53 52 6 necessitatibus quas et sunt at voluptatem 50
54 53 6 est vel sequi voluptatem nemo quam molestiae modi enim 50
55 54 6 aut non illo amet perferendis 50
56 55 6 qui culpa itaque omnis in nesciunt architecto error 50
57 56 6 omnis qui maiores tempora officiis omnis rerum sed repellat 50
58 57 6 libero excepturi voluptatem est architecto quae voluptatum officia tempora 50
59 58 6 nulla illo consequatur aspernatur veritatis aut error delectus et 50
60 59 6 eligendi similique provident nihil 50
61 60 6 omnis mollitia sunt aliquid eum consequatur fugit minus laudantium 50
62 61 7 delectus iusto et 50
63 62 7 eos ea non recusandae iste ut quasi 50
64 63 7 velit est quam 50
65 64 7 autem voluptatem amet iure quae 50
66 65 7 voluptates delectus iure iste qui 50
67 66 7 velit sed quia dolor dolores delectus 50
68 67 7 ad voluptas nostrum et nihil 50
69 68 7 qui quasi nihil aut voluptatum sit dolore minima 50
70 69 7 qui aut est 50
71 70 7 et deleniti unde 50
72 71 8 et vel corporis 50
73 72 8 unde exercitationem ut 50
74 73 8 quos omnis officia 50
75 74 8 quia est eius vitae dolor 50
76 75 8 aut quia expedita non 50
77 76 8 dolorem magnam facere itaque ut reprehenderit tenetur corrupti 50
78 77 8 cupiditate sapiente maiores iusto ducimus cum excepturi veritatis quia 50
79 78 8 est minima eius possimus ea ratione velit et 50
80 79 8 ipsa quae voluptas natus ut suscipit soluta quia quidem 50
81 80 8 id nihil reprehenderit 50
82 81 9 quibusdam sapiente et 50
83 82 9 recusandae consequatur vel amet unde 50
84 83 9 aperiam odio fugiat 50
85 84 9 est et at eos expedita 50
86 85 9 qui voluptatem consequatur aut ab quis temporibus praesentium 50
87 86 9 eligendi mollitia alias aspernatur vel ut iusto 50
88 87 9 aut aut architecto 50
89 88 9 quas perspiciatis optio 50
90 89 9 sit optio id voluptatem est eum et 50
91 90 9 est vel dignissimos 50
92 91 10 repellendus praesentium debitis officiis 50
93 92 10 incidunt et et eligendi assumenda soluta quia recusandae 50
94 93 10 nisi qui dolores perspiciatis 50
95 94 10 quisquam a dolores et earum vitae 50
96 95 10 consectetur vel rerum qui aperiam modi eos aspernatur ipsa 50
97 96 10 unde et ut molestiae est molestias voluptatem sint 50
98 97 10 est quod aut 50
99 98 10 omnis quia possimus nesciunt deleniti assumenda sed autem 50
100 99 10 consectetur ut id impedit dolores sit ad ex aut 50
101 100 10 enim repellat iste 50
@@ -0,0 +1,602 @@
[
{
"id": 1,
"userId": 1,
"title": "quidem molestiae enim",
"photo_count": 50
},
{
"id": 2,
"userId": 1,
"title": "sunt qui excepturi placeat culpa",
"photo_count": 50
},
{
"id": 3,
"userId": 1,
"title": "omnis laborum odio",
"photo_count": 50
},
{
"id": 4,
"userId": 1,
"title": "non esse culpa molestiae omnis sed optio",
"photo_count": 50
},
{
"id": 5,
"userId": 1,
"title": "eaque aut omnis a",
"photo_count": 50
},
{
"id": 6,
"userId": 1,
"title": "natus impedit quibusdam illo est",
"photo_count": 50
},
{
"id": 7,
"userId": 1,
"title": "quibusdam autem aliquid et et quia",
"photo_count": 50
},
{
"id": 8,
"userId": 1,
"title": "qui fuga est a eum",
"photo_count": 50
},
{
"id": 9,
"userId": 1,
"title": "saepe unde necessitatibus rem",
"photo_count": 50
},
{
"id": 10,
"userId": 1,
"title": "distinctio laborum qui",
"photo_count": 50
},
{
"id": 11,
"userId": 2,
"title": "quam nostrum impedit mollitia quod et dolor",
"photo_count": 50
},
{
"id": 12,
"userId": 2,
"title": "consequatur autem doloribus natus consectetur",
"photo_count": 50
},
{
"id": 13,
"userId": 2,
"title": "ab rerum non rerum consequatur ut ea unde",
"photo_count": 50
},
{
"id": 14,
"userId": 2,
"title": "ducimus molestias eos animi atque nihil",
"photo_count": 50
},
{
"id": 15,
"userId": 2,
"title": "ut pariatur rerum ipsum natus repellendus praesentium",
"photo_count": 50
},
{
"id": 16,
"userId": 2,
"title": "voluptatem aut maxime inventore autem magnam atque repellat",
"photo_count": 50
},
{
"id": 17,
"userId": 2,
"title": "aut minima voluptatem ut velit",
"photo_count": 50
},
{
"id": 18,
"userId": 2,
"title": "nesciunt quia et doloremque",
"photo_count": 50
},
{
"id": 19,
"userId": 2,
"title": "velit pariatur quaerat similique libero omnis quia",
"photo_count": 50
},
{
"id": 20,
"userId": 2,
"title": "voluptas rerum iure ut enim",
"photo_count": 50
},
{
"id": 21,
"userId": 3,
"title": "repudiandae voluptatem optio est consequatur rem in temporibus et",
"photo_count": 50
},
{
"id": 22,
"userId": 3,
"title": "et rem non provident vel ut",
"photo_count": 50
},
{
"id": 23,
"userId": 3,
"title": "incidunt quisquam hic adipisci sequi",
"photo_count": 50
},
{
"id": 24,
"userId": 3,
"title": "dolores ut et facere placeat",
"photo_count": 50
},
{
"id": 25,
"userId": 3,
"title": "vero maxime id possimus sunt neque et consequatur",
"photo_count": 50
},
{
"id": 26,
"userId": 3,
"title": "quibusdam saepe ipsa vel harum",
"photo_count": 50
},
{
"id": 27,
"userId": 3,
"title": "id non nostrum expedita",
"photo_count": 50
},
{
"id": 28,
"userId": 3,
"title": "omnis neque exercitationem sed dolor atque maxime aut cum",
"photo_count": 50
},
{
"id": 29,
"userId": 3,
"title": "inventore ut quasi magnam itaque est fugit",
"photo_count": 50
},
{
"id": 30,
"userId": 3,
"title": "tempora assumenda et similique odit distinctio error",
"photo_count": 50
},
{
"id": 31,
"userId": 4,
"title": "adipisci laborum fuga laboriosam",
"photo_count": 50
},
{
"id": 32,
"userId": 4,
"title": "reiciendis dolores a ut qui debitis non quo labore",
"photo_count": 50
},
{
"id": 33,
"userId": 4,
"title": "iste eos nostrum",
"photo_count": 50
},
{
"id": 34,
"userId": 4,
"title": "cumque voluptatibus rerum architecto blanditiis",
"photo_count": 50
},
{
"id": 35,
"userId": 4,
"title": "et impedit nisi quae magni necessitatibus sed aut pariatur",
"photo_count": 50
},
{
"id": 36,
"userId": 4,
"title": "nihil cupiditate voluptate neque",
"photo_count": 50
},
{
"id": 37,
"userId": 4,
"title": "est placeat dicta ut nisi rerum iste",
"photo_count": 50
},
{
"id": 38,
"userId": 4,
"title": "unde a sequi id",
"photo_count": 50
},
{
"id": 39,
"userId": 4,
"title": "ratione porro illum labore eum aperiam sed",
"photo_count": 50
},
{
"id": 40,
"userId": 4,
"title": "voluptas neque et sint aut quo odit",
"photo_count": 50
},
{
"id": 41,
"userId": 5,
"title": "ea voluptates maiores eos accusantium officiis tempore mollitia consequatur",
"photo_count": 50
},
{
"id": 42,
"userId": 5,
"title": "tenetur explicabo ea",
"photo_count": 50
},
{
"id": 43,
"userId": 5,
"title": "aperiam doloremque nihil",
"photo_count": 50
},
{
"id": 44,
"userId": 5,
"title": "sapiente cum numquam officia consequatur vel natus quos suscipit",
"photo_count": 50
},
{
"id": 45,
"userId": 5,
"title": "tenetur quos ea unde est enim corrupti qui",
"photo_count": 50
},
{
"id": 46,
"userId": 5,
"title": "molestiae voluptate non",
"photo_count": 50
},
{
"id": 47,
"userId": 5,
"title": "temporibus molestiae aut",
"photo_count": 50
},
{
"id": 48,
"userId": 5,
"title": "modi consequatur culpa aut quam soluta alias perspiciatis laudantium",
"photo_count": 50
},
{
"id": 49,
"userId": 5,
"title": "ut aut vero repudiandae voluptas ullam voluptas at consequatur",
"photo_count": 50
},
{
"id": 50,
"userId": 5,
"title": "sed qui sed quas sit ducimus dolor",
"photo_count": 50
},
{
"id": 51,
"userId": 6,
"title": "odit laboriosam sint quia cupiditate animi quis",
"photo_count": 50
},
{
"id": 52,
"userId": 6,
"title": "necessitatibus quas et sunt at voluptatem",
"photo_count": 50
},
{
"id": 53,
"userId": 6,
"title": "est vel sequi voluptatem nemo quam molestiae modi enim",
"photo_count": 50
},
{
"id": 54,
"userId": 6,
"title": "aut non illo amet perferendis",
"photo_count": 50
},
{
"id": 55,
"userId": 6,
"title": "qui culpa itaque omnis in nesciunt architecto error",
"photo_count": 50
},
{
"id": 56,
"userId": 6,
"title": "omnis qui maiores tempora officiis omnis rerum sed repellat",
"photo_count": 50
},
{
"id": 57,
"userId": 6,
"title": "libero excepturi voluptatem est architecto quae voluptatum officia tempora",
"photo_count": 50
},
{
"id": 58,
"userId": 6,
"title": "nulla illo consequatur aspernatur veritatis aut error delectus et",
"photo_count": 50
},
{
"id": 59,
"userId": 6,
"title": "eligendi similique provident nihil",
"photo_count": 50
},
{
"id": 60,
"userId": 6,
"title": "omnis mollitia sunt aliquid eum consequatur fugit minus laudantium",
"photo_count": 50
},
{
"id": 61,
"userId": 7,
"title": "delectus iusto et",
"photo_count": 50
},
{
"id": 62,
"userId": 7,
"title": "eos ea non recusandae iste ut quasi",
"photo_count": 50
},
{
"id": 63,
"userId": 7,
"title": "velit est quam",
"photo_count": 50
},
{
"id": 64,
"userId": 7,
"title": "autem voluptatem amet iure quae",
"photo_count": 50
},
{
"id": 65,
"userId": 7,
"title": "voluptates delectus iure iste qui",
"photo_count": 50
},
{
"id": 66,
"userId": 7,
"title": "velit sed quia dolor dolores delectus",
"photo_count": 50
},
{
"id": 67,
"userId": 7,
"title": "ad voluptas nostrum et nihil",
"photo_count": 50
},
{
"id": 68,
"userId": 7,
"title": "qui quasi nihil aut voluptatum sit dolore minima",
"photo_count": 50
},
{
"id": 69,
"userId": 7,
"title": "qui aut est",
"photo_count": 50
},
{
"id": 70,
"userId": 7,
"title": "et deleniti unde",
"photo_count": 50
},
{
"id": 71,
"userId": 8,
"title": "et vel corporis",
"photo_count": 50
},
{
"id": 72,
"userId": 8,
"title": "unde exercitationem ut",
"photo_count": 50
},
{
"id": 73,
"userId": 8,
"title": "quos omnis officia",
"photo_count": 50
},
{
"id": 74,
"userId": 8,
"title": "quia est eius vitae dolor",
"photo_count": 50
},
{
"id": 75,
"userId": 8,
"title": "aut quia expedita non",
"photo_count": 50
},
{
"id": 76,
"userId": 8,
"title": "dolorem magnam facere itaque ut reprehenderit tenetur corrupti",
"photo_count": 50
},
{
"id": 77,
"userId": 8,
"title": "cupiditate sapiente maiores iusto ducimus cum excepturi veritatis quia",
"photo_count": 50
},
{
"id": 78,
"userId": 8,
"title": "est minima eius possimus ea ratione velit et",
"photo_count": 50
},
{
"id": 79,
"userId": 8,
"title": "ipsa quae voluptas natus ut suscipit soluta quia quidem",
"photo_count": 50
},
{
"id": 80,
"userId": 8,
"title": "id nihil reprehenderit",
"photo_count": 50
},
{
"id": 81,
"userId": 9,
"title": "quibusdam sapiente et",
"photo_count": 50
},
{
"id": 82,
"userId": 9,
"title": "recusandae consequatur vel amet unde",
"photo_count": 50
},
{
"id": 83,
"userId": 9,
"title": "aperiam odio fugiat",
"photo_count": 50
},
{
"id": 84,
"userId": 9,
"title": "est et at eos expedita",
"photo_count": 50
},
{
"id": 85,
"userId": 9,
"title": "qui voluptatem consequatur aut ab quis temporibus praesentium",
"photo_count": 50
},
{
"id": 86,
"userId": 9,
"title": "eligendi mollitia alias aspernatur vel ut iusto",
"photo_count": 50
},
{
"id": 87,
"userId": 9,
"title": "aut aut architecto",
"photo_count": 50
},
{
"id": 88,
"userId": 9,
"title": "quas perspiciatis optio",
"photo_count": 50
},
{
"id": 89,
"userId": 9,
"title": "sit optio id voluptatem est eum et",
"photo_count": 50
},
{
"id": 90,
"userId": 9,
"title": "est vel dignissimos",
"photo_count": 50
},
{
"id": 91,
"userId": 10,
"title": "repellendus praesentium debitis officiis",
"photo_count": 50
},
{
"id": 92,
"userId": 10,
"title": "incidunt et et eligendi assumenda soluta quia recusandae",
"photo_count": 50
},
{
"id": 93,
"userId": 10,
"title": "nisi qui dolores perspiciatis",
"photo_count": 50
},
{
"id": 94,
"userId": 10,
"title": "quisquam a dolores et earum vitae",
"photo_count": 50
},
{
"id": 95,
"userId": 10,
"title": "consectetur vel rerum qui aperiam modi eos aspernatur ipsa",
"photo_count": 50
},
{
"id": 96,
"userId": 10,
"title": "unde et ut molestiae est molestias voluptatem sint",
"photo_count": 50
},
{
"id": 97,
"userId": 10,
"title": "est quod aut",
"photo_count": 50
},
{
"id": 98,
"userId": 10,
"title": "omnis quia possimus nesciunt deleniti assumenda sed autem",
"photo_count": 50
},
{
"id": 99,
"userId": 10,
"title": "consectetur ut id impedit dolores sit ad ex aut",
"photo_count": 50
},
{
"id": 100,
"userId": 10,
"title": "enim repellat iste",
"photo_count": 50
}
]
@@ -0,0 +1,43 @@
# Albums → Photos join (extract artifact) — ledger lane
Per-album table: which album, its author (`userId`), its title, and the number of
photos it contains. One row per album.
## Sources
- `https://jsonplaceholder.typicode.com/albums` — 100 albums
- `https://jsonplaceholder.typicode.com/photos` — 5000 photos
Join key: `photos.albumId == albums.id`.
## Columns
| Column | Meaning |
|---|---|
| `id` | album id (1–100) |
| `userId` | album author |
| `title` | album title |
| `photo_count` | number of photos in the album |
## Result (100 rows)
- 100 albums, 5000 photos, 100 distinct album ids.
- `photo_count` is **50 for every album** (uniform).
## Artifacts (reproduced by `extract_albums_photo_counts.py`)
- `2026-10-31-albums-photo-counts.csv` — the table (CSV)
- `2026-10-31-albums-photo-counts.json` — the same table (JSON)
## Verification (single command, exit 0 = correct)
Run from the repo root:
```bash
bash wizard-work/ledger/CHECK-albums-photo-counts
```
The check re-fetches both endpoints, re-derives the table, and compares the
committed CSV and JSON against the fresh extraction (parsed, so line endings and
quoting do not affect the verdict). It also asserts the row counts, the total
photo count and the uniform `photo_count` claim above.
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# CHECK -- albums->photos extract (Extract lane, 2026-10-31).
# Re-fetches both JSONPlaceholder endpoints, re-derives the per-album table, and
# compares the committed CSV + JSON against the fresh extraction. Exits 0 only
# when the committed artifacts match. Exit code IS the verdict.
set -u
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
cd "$DIR/../.." || exit 1
python3 wizard-work/ledger/verify_albums_photo_counts.py
@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""Extract: JSONPlaceholder albums -> photos join.
Builds a per-album table: which album, its author (userId), its title, and the
number of photos it contains. Sources:
https://jsonplaceholder.typicode.com/albums (100 albums)
https://jsonplaceholder.typicode.com/photos (5000 photos)
Join key: photos.albumId == albums.id
One row per album (100 rows). photo_count is 50 for every album (uniform).
Outputs: CSV + JSON of the same table.
"""
import csv
import json
import os
import urllib.request
from collections import Counter, defaultdict
ALBUMS_URL = "https://jsonplaceholder.typicode.com/albums"
PHOTOS_URL = "https://jsonplaceholder.typicode.com/photos"
BASE = os.path.dirname(os.path.abspath(__file__))
def fetch(url):
req = urllib.request.Request(url, headers={"User-Agent": "ledger/extract"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
def main():
albums = fetch(ALBUMS_URL)
photos = fetch(PHOTOS_URL)
# sanity: every photo must resolve to a known album
album_ids = {a["id"] for a in albums}
unknown = sorted({p["albumId"] for p in photos} - album_ids)
if unknown:
raise SystemExit("photo albumIds not in albums: %s" % unknown[:5])
counts = Counter(p["albumId"] for p in photos)
total = sum(counts.values())
rows = []
for a in sorted(albums, key=lambda x: x["id"]):
rows.append({
"id": a["id"],
"userId": a["userId"],
"title": a["title"],
"photo_count": counts.get(a["id"], 0),
})
csv_path = os.path.join(BASE, "2026-10-31-albums-photo-counts.csv")
json_path = os.path.join(BASE, "2026-10-31-albums-photo-counts.json")
with open(csv_path, "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=["id", "userId", "title", "photo_count"])
w.writeheader()
w.writerows(rows)
payload = json.dumps(rows, ensure_ascii=False, indent=2).encode("utf-8")
with open(json_path, "wb") as f:
f.write(payload)
with open(csv_path, encoding="utf-8") as f:
csv_bytes = len(f.read())
print("wrote %s (%d bytes, %d rows)" % (csv_path, csv_bytes, len(rows)))
print("wrote %s (%d bytes)" % (json_path, len(payload)))
print("total photos = %d across %d albums (uniform photo_count=%d)"
% (total, len(rows), rows[0]["photo_count"] if rows else None))
if __name__ == "__main__":
main()
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Independent verifier for the albums->photos extract artifact (ledger lane).
Re-fetches both JSONPlaceholder endpoints, re-derives the per-album table, and
compares the committed CSV + JSON against the fresh extraction (parsed, so line
endings and quoting do not affect the verdict). Also asserts the row counts,
the total photo count and the uniform photo_count claim above. Exits 0 only when
the committed artifacts match the fresh extraction.
"""
import csv
import json
import sys
import urllib.request
from collections import Counter
BASE = "/root/troupe-standup/wizard-work/ledger"
CSV_PATH = BASE + "/2026-10-31-albums-photo-counts.csv"
JSON_PATH = BASE + "/2026-10-31-albums-photo-counts.json"
ALBUMS_URL = "https://jsonplaceholder.typicode.com/albums"
PHOTOS_URL = "https://jsonplaceholder.typicode.com/photos"
def fetch(url):
req = urllib.request.Request(url, headers={"User-Agent": "ledger/extract"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
def read_csv_rows(path):
with open(path, encoding="utf-8") as f:
return list(csv.DictReader(f))
def main():
albums = fetch(ALBUMS_URL)
photos = fetch(PHOTOS_URL)
album_ids = {a["id"] for a in albums}
unknown = sorted({p["albumId"] for p in photos} - album_ids)
if unknown:
print("FAIL: photo albumIds not present in albums: %s" % unknown[:5])
return 1
counts = Counter(p["albumId"] for p in photos)
total = sum(counts.values())
# Fresh extraction result (same field types the extractor writes).
raw_rows = []
for a in sorted(albums, key=lambda x: x["id"]):
raw_rows.append({
"id": a["id"],
"userId": a["userId"],
"title": a["title"],
"photo_count": counts.get(a["id"], 0),
})
if len(albums) != 100:
print("FAIL: expected 100 albums, got %d" % len(albums))
return 1
if len(photos) != 5000:
print("FAIL: expected 5000 photos, got %d" % len(photos))
return 1
if len(counts) != 100:
print("FAIL: expected 100 distinct album ids with photos, got %d" % len(counts))
return 1
if len(set(counts.values())) != 1:
print("FAIL: photo_count not uniform, values=%s" % sorted(set(counts.values())))
return 1
# CSV: committed file parsed with DictReader -> all strings.
csv_rows = []
for r in raw_rows:
csv_rows.append({
"id": str(r["id"]),
"userId": str(r["userId"]),
"title": r["title"],
"photo_count": str(r["photo_count"]),
})
if read_csv_rows(CSV_PATH) != csv_rows:
print("FAIL: CSV does not match fresh extraction")
return 1
# JSON: committed file parsed -> ints for id/userId/photo_count.
if json.load(open(JSON_PATH, encoding="utf-8")) != raw_rows:
print("FAIL: JSON does not match fresh extraction")
return 1
print("OK: %d rows match the fresh extraction (uniform photo_count=50, %d photos)"
% (len(raw_rows), total))
return 0
if __name__ == "__main__":
sys.exit(main())