diff --git a/.github/workflows/theseus-engine.yml b/.github/workflows/theseus-engine.yml index efde8db..817949a 100644 --- a/.github/workflows/theseus-engine.yml +++ b/.github/workflows/theseus-engine.yml @@ -33,6 +33,13 @@ jobs: - name: Run theseus multi-repo analysis (delta load) run: poetry run python scripts/analyse_repository.py + - name: Update living fossils (survivor check) + run: | + # Re-blame HEAD for every repo and update the survivor fossil only if + # the file:line:commit has actually changed since the last run. + # Genesis (historical fossil) is left completely untouched. + poetry run python scripts/add_fossils.py --update-survivor + - name: Commit and push data updates run: | git config --local user.email "action@github.com" diff --git a/app.js b/app.js index 0446967..3c58519 100644 --- a/app.js +++ b/app.js @@ -30,12 +30,13 @@ class TheseusVisualizer { async init() { try { - const response = await fetch('data/manifest.json'); + const response = await fetch('theseus.config.json'); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } let data = await response.json(); - this.manifest = Array.isArray(data) ? data : [data]; + // Fallback for backward compatibility, but normally expected to be under 'repositories' + this.manifest = data.repositories || (Array.isArray(data) ? data : [data]); this.renderSelectors(); this.setupModeToggle(); @@ -406,7 +407,7 @@ class TheseusVisualizer { const idx = bisect(this.points, date, 1); const d0 = this.points[idx - 1]; const d1 = this.points[idx]; - + // Handle single-point or edge cases if (!d0 && !d1) return; let d; @@ -447,8 +448,8 @@ class TheseusVisualizer { let refactorHTML = ''; if (originalVal === 0) { refactorHTML = ` -
Ship of Theseus: The Great Rebirth The original source code is now entirely gone.
Is this still the same codebase? @@ -456,8 +457,8 @@ class TheseusVisualizer { `; } else if (isRefactor) { refactorHTML = ` -
Ship of Theseus: Major Refactor A significant part of the original source was refactored here.
How much can you change before the identity shifts? @@ -653,24 +654,47 @@ class TheseusVisualizer { const repoPath = repoInfo ? repoInfo.repo : null; const buildLink = (fossil) => { - if (!fossil.file) return '--'; - const display = `${fossil.file}:${fossil.line}`; - if (!repoPath || !fossil.commit) return display; - const url = `https://github.com/${repoPath}/blob/${fossil.commit}/${fossil.file}#L${fossil.line}`; - return `${display}`; + const display = fossil.file ? `${fossil.file}:${fossil.line}` : '--'; + + // No file or no repo → plain text node, nothing to link + if (!fossil.file || !repoPath) return document.createTextNode(display); + + const linkCommit = fossil.view_commit || fossil.commit; + if (!linkCommit) return document.createTextNode(display); + + // URL-encode the file path (preserve /, encode special chars per segment) + // and the commit ref (branch names can contain slashes so encode fully) + const safeFile = fossil.file.split('/').map(encodeURIComponent).join('/'); + const safeCommit = encodeURIComponent(linkCommit); + const safeLine = parseInt(fossil.line, 10) || 0; + const url = `https://github.com/${repoPath}/blob/${safeCommit}/${safeFile}#L${safeLine}`; + + const a = document.createElement('a'); + a.href = url; + a.target = '_blank'; + a.rel = 'noopener noreferrer'; + a.textContent = display; + a.style.color = 'inherit'; + a.style.textDecoration = 'underline'; + a.style.textDecorationColor = 'rgba(255,255,255,0.2)'; + a.style.transition = 'color 0.3s ease'; + a.addEventListener('mouseover', () => { a.style.color = 'var(--accent-cyan)'; }); + a.addEventListener('mouseout', () => { a.style.color = 'inherit'; }); + return a; }; - // Genesis (The Origin) + // Genesis (Historical Fossil) — show the pinned blame commit hash (frozen in history) document.getElementById('genesis-year').textContent = genesis.year || '----'; - document.getElementById('genesis-file').innerHTML = buildLink(genesis); + document.getElementById('genesis-file').replaceChildren(buildLink(genesis)); document.getElementById('genesis-content').textContent = genesis.content ? genesis.content.trim() : 'No fossil data'; document.getElementById('genesis-commit').textContent = genesis.commit || ''; - // Survivor (The Current) + // Survivor (Living Fossil) — show branch name (e.g. "main"), not old blame hash document.getElementById('survivor-year').textContent = survivor.year || '----'; - document.getElementById('survivor-file').innerHTML = buildLink(survivor); + document.getElementById('survivor-file').replaceChildren(buildLink(survivor)); document.getElementById('survivor-content').textContent = survivor.content ? survivor.content.trim() : 'No fossil data'; - document.getElementById('survivor-commit').textContent = survivor.commit || ''; + document.getElementById('survivor-commit').textContent = survivor.view_commit || survivor.commit || ''; + } createSVGElement(tag, attrs = {}) { @@ -680,7 +704,20 @@ class TheseusVisualizer { } showLoading(show) { - this.loadingState.classList.toggle('hidden', !show); + if (show) { + this.loadingState.classList.remove('hidden'); + // Also hide the chart container while skeleton shows + const chartContainer = document.getElementById('chart-container'); + if (chartContainer) chartContainer.style.opacity = '0'; + } else { + this.loadingState.classList.add('hidden'); + // Fade the chart back in smoothly + const chartContainer = document.getElementById('chart-container'); + if (chartContainer) { + chartContainer.style.transition = 'opacity 0.35s ease'; + chartContainer.style.opacity = '1'; + } + } } showError(msg) { diff --git a/data/claude-code_data.json b/data/claude-code_data.json index c07b9c6..9677d96 100644 --- a/data/claude-code_data.json +++ b/data/claude-code_data.json @@ -1 +1 @@ -{"snapshots":[{"snapshot_date":"2025-02","composition":{"2025":315}},{"snapshot_date":"2025-03","composition":{"2025":368}},{"snapshot_date":"2025-04","composition":{"2025":455}},{"snapshot_date":"2025-05","composition":{"2025":756}},{"snapshot_date":"2025-06","composition":{"2025":876}},{"snapshot_date":"2025-07","composition":{"2025":77387}},{"snapshot_date":"2025-08","composition":{"2025":77900}},{"snapshot_date":"2025-09","composition":{"2025":78823}},{"snapshot_date":"2025-10","composition":{"2025":56350}},{"snapshot_date":"2025-11","composition":{"2025":60301}},{"snapshot_date":"2025-12","composition":{"2025":86284}},{"snapshot_date":"2026-01","composition":{"2025":86626,"2026":35}},{"snapshot_date":"2026-02","composition":{"2025":86420,"2026":653}},{"snapshot_date":"2026-03","composition":{"2025":86142,"2026":1705}},{"snapshot_date":"2026-04","composition":{"2025":86142,"2026":2431}}],"fossils":{"genesis":{"timestamp":1740245022,"file":".devcontainer/Dockerfile","content":"FROM node:20","year":"2025","commit":"bd5ca70","line":1},"survivor":{"timestamp":1740245022,"file":".devcontainer/Dockerfile","content":"FROM node:20","year":"2025","commit":"bd5ca70","line":1}}} \ No newline at end of file +{"snapshots":[{"snapshot_date":"2025-02","composition":{"2025":315}},{"snapshot_date":"2025-03","composition":{"2025":368}},{"snapshot_date":"2025-04","composition":{"2025":455}},{"snapshot_date":"2025-05","composition":{"2025":756}},{"snapshot_date":"2025-06","composition":{"2025":876}},{"snapshot_date":"2025-07","composition":{"2025":77387}},{"snapshot_date":"2025-08","composition":{"2025":77900}},{"snapshot_date":"2025-09","composition":{"2025":78823}},{"snapshot_date":"2025-10","composition":{"2025":56350}},{"snapshot_date":"2025-11","composition":{"2025":60301}},{"snapshot_date":"2025-12","composition":{"2025":86284}},{"snapshot_date":"2026-01","composition":{"2025":86626,"2026":35}},{"snapshot_date":"2026-02","composition":{"2025":86420,"2026":653}},{"snapshot_date":"2026-03","composition":{"2025":86142,"2026":1705}},{"snapshot_date":"2026-04","composition":{"2025":86142,"2026":2431}}],"fossils":{"genesis":{"timestamp":1740245022,"file":".devcontainer/Dockerfile","content":"FROM node:20","year":"2025","commit":"bd5ca70","view_commit":"bd5ca708adf82c4b81857abf40fe36d9d9cc3d1c","line":1},"survivor":{"timestamp":1740245022,"file":".devcontainer/init-firewall.sh","content":"#!/bin/bash","year":"2025","commit":"bd5ca70","view_commit":"main","line":1}}} \ No newline at end of file diff --git a/data/langchain_data.json b/data/langchain_data.json index 8f840ad..a84de50 100644 --- a/data/langchain_data.json +++ b/data/langchain_data.json @@ -1 +1 @@ -{"snapshots":[{"snapshot_date":"2022-12","composition":{"2022":15774}},{"snapshot_date":"2023-03","composition":{"2022":28253,"2023":61969}},{"snapshot_date":"2023-06","composition":{"2023":487975,"2022":21681}},{"snapshot_date":"2023-09","composition":{"2023":681274,"2022":16291}},{"snapshot_date":"2023-12","composition":{"2023":881194,"2022":14704}},{"snapshot_date":"2024-03","composition":{"2024":357436,"2023":944924,"2022":10842}},{"snapshot_date":"2024-06","composition":{"2023":826677,"2024":645423,"2022":7685}},{"snapshot_date":"2024-09","composition":{"2023":686097,"2024":664907,"2022":7295}},{"snapshot_date":"2024-12","composition":{"2024":683157,"2023":546569,"2022":7138}},{"snapshot_date":"2025-01","composition":{"2024":720654,"2023":544169,"2022":7122,"2025":5}},{"snapshot_date":"2025-02","composition":{"2023":542683,"2024":707703,"2025":56681,"2022":7106}},{"snapshot_date":"2025-03","composition":{"2023":533418,"2024":657044,"2025":124237,"2022":6529}},{"snapshot_date":"2025-04","composition":{"2023":491914,"2024":630484,"2025":129671,"2022":6346}},{"snapshot_date":"2025-05","composition":{"2024":448680,"2025":144491,"2023":315772,"2022":5672}},{"snapshot_date":"2025-06","composition":{"2023":313764,"2024":444312,"2025":158141,"2022":5671}},{"snapshot_date":"2025-07","composition":{"2023":313208,"2024":441747,"2025":170384,"2022":5671}},{"snapshot_date":"2025-08","composition":{"2025":203640,"2023":308982,"2024":435295,"2022":5429}},{"snapshot_date":"2025-09","composition":{"2025":222619,"2023":308625,"2024":433511,"2022":5427}},{"snapshot_date":"2025-10","composition":{"2023":306859,"2025":253209,"2024":429458,"2022":5409}},{"snapshot_date":"2025-11","composition":{"2025":213274,"2023":83597,"2024":138301,"2022":3632}},{"snapshot_date":"2025-12","composition":{"2025":213376,"2023":83259,"2024":137738,"2022":3489}},{"snapshot_date":"2026-01","composition":{"2025":221792,"2023":82989,"2024":136828,"2022":3485,"2026":268}},{"snapshot_date":"2026-02","composition":{"2024":128469,"2023":81140,"2025":207607,"2026":12545,"2022":3485}},{"snapshot_date":"2026-03","composition":{"2023":81121,"2025":198803,"2024":126788,"2026":35860,"2022":3484}},{"snapshot_date":"2026-04","composition":{"2025":195106,"2023":81079,"2024":126636,"2026":47792,"2022":3475}}],"fossils":{"genesis":{"timestamp":1666648275,"file":".flake8","content":"[flake8]","year":"2022","commit":"18aeb72","line":1},"survivor":{"timestamp":1666648275,"file":".flake8","content":"[flake8]","year":"2022","commit":"18aeb72","line":1}}} \ No newline at end of file +{"snapshots":[{"snapshot_date":"2022-12","composition":{"2022":15774}},{"snapshot_date":"2023-03","composition":{"2022":28253,"2023":61969}},{"snapshot_date":"2023-06","composition":{"2023":487975,"2022":21681}},{"snapshot_date":"2023-09","composition":{"2023":681274,"2022":16291}},{"snapshot_date":"2023-12","composition":{"2023":881194,"2022":14704}},{"snapshot_date":"2024-03","composition":{"2024":357436,"2023":944924,"2022":10842}},{"snapshot_date":"2024-06","composition":{"2023":826677,"2024":645423,"2022":7685}},{"snapshot_date":"2024-09","composition":{"2023":686097,"2024":664907,"2022":7295}},{"snapshot_date":"2024-12","composition":{"2024":683157,"2023":546569,"2022":7138}},{"snapshot_date":"2025-01","composition":{"2024":720654,"2023":544169,"2022":7122,"2025":5}},{"snapshot_date":"2025-02","composition":{"2023":542683,"2024":707703,"2025":56681,"2022":7106}},{"snapshot_date":"2025-03","composition":{"2023":533418,"2024":657044,"2025":124237,"2022":6529}},{"snapshot_date":"2025-04","composition":{"2023":491914,"2024":630484,"2025":129671,"2022":6346}},{"snapshot_date":"2025-05","composition":{"2024":448680,"2025":144491,"2023":315772,"2022":5672}},{"snapshot_date":"2025-06","composition":{"2023":313764,"2024":444312,"2025":158141,"2022":5671}},{"snapshot_date":"2025-07","composition":{"2023":313208,"2024":441747,"2025":170384,"2022":5671}},{"snapshot_date":"2025-08","composition":{"2025":203640,"2023":308982,"2024":435295,"2022":5429}},{"snapshot_date":"2025-09","composition":{"2025":222619,"2023":308625,"2024":433511,"2022":5427}},{"snapshot_date":"2025-10","composition":{"2023":306859,"2025":253209,"2024":429458,"2022":5409}},{"snapshot_date":"2025-11","composition":{"2025":213274,"2023":83597,"2024":138301,"2022":3632}},{"snapshot_date":"2025-12","composition":{"2025":213376,"2023":83259,"2024":137738,"2022":3489}},{"snapshot_date":"2026-01","composition":{"2025":221792,"2023":82989,"2024":136828,"2022":3485,"2026":268}},{"snapshot_date":"2026-02","composition":{"2024":128469,"2023":81140,"2025":207607,"2026":12545,"2022":3485}},{"snapshot_date":"2026-03","composition":{"2023":81121,"2025":198803,"2024":126788,"2026":35860,"2022":3484}},{"snapshot_date":"2026-04","composition":{"2025":195106,"2023":81079,"2024":126636,"2026":47792,"2022":3475}}],"fossils":{"genesis":{"timestamp":1666648275,"file":".flake8","content":"[flake8]","year":"2022","commit":"18aeb72","view_commit":"18aeb720126a68201c7e3b5a617139c27c779496","line":1},"survivor":{"timestamp":1666648275,"file":".github/workflows/_lint.yml","content":"jobs:","year":"2022","commit":"18aeb72","view_commit":"master","line":33}}} \ No newline at end of file diff --git a/data/manifest.json b/data/manifest.json deleted file mode 100644 index af9634e..0000000 --- a/data/manifest.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "name": "langchain", - "file": "langchain_data.json", - "description": "Framework for developing LLM-driven applications and agents.", - "repo": "langchain-ai/langchain" - }, - { - "name": "react", - "file": "react_data.json", - "description": "Component-based JavaScript library for building user interfaces.", - "repo": "facebook/react" - }, - { - "name": "numpy", - "file": "numpy_data.json", - "description": "The fundamental package for scientific computing in Python.", - "repo": "numpy/numpy" - }, - { - "name": "zed", - "file": "zed_data.json", - "description": "High-performance, GPU-accelerated code editor for teamwork.", - "repo": "zed-industries/zed" - }, - { - "name": "claude-code", - "file": "claude-code_data.json", - "description": "Claude's agentic CLI tool for local coding tasks.", - "repo": "anthropics/claude-code" - } -] \ No newline at end of file diff --git a/data/numpy_data.json b/data/numpy_data.json index cb3bd2f..7e35068 100644 --- a/data/numpy_data.json +++ b/data/numpy_data.json @@ -1 +1 @@ -{"snapshots":[{"snapshot_date":"2001-12","composition":{"2001":1865}},{"snapshot_date":"2002-03","composition":{"2002":94339,"2001":1472}},{"snapshot_date":"2002-06","composition":{"2002":102869,"2001":1179}},{"snapshot_date":"2002-09","composition":{"2002":130360,"2001":1167}},{"snapshot_date":"2002-12","composition":{"2002":132966,"2001":1130}},{"snapshot_date":"2003-03","composition":{"2003":2305,"2002":132607,"2001":1052}},{"snapshot_date":"2003-06","composition":{"2002":132569,"2003":2688,"2001":1047}},{"snapshot_date":"2003-09","composition":{"2003":3793,"2002":132461,"2001":1036}},{"snapshot_date":"2003-12","composition":{"2003":5328,"2002":131017,"2001":1009}},{"snapshot_date":"2004-03","composition":{"2003":3964,"2002":129547,"2004":5960,"2001":449}},{"snapshot_date":"2004-06","composition":{"2004":9689,"2002":129500,"2003":3916,"2001":443}},{"snapshot_date":"2004-09","composition":{"2003":3868,"2002":128923,"2004":10562,"2001":443}},{"snapshot_date":"2004-12","composition":{"2004":13272,"2002":128551,"2003":3680,"2001":437}},{"snapshot_date":"2005-03","composition":{"2002":128546,"2004":13125,"2003":3676,"2005":352,"2001":437}},{"snapshot_date":"2005-06","composition":{"2004":13106,"2005":1801,"2002":128527,"2003":3655,"2001":437}},{"snapshot_date":"2005-09","composition":{"2005":150609,"2002":120948,"2004":2743,"2003":2178}},{"snapshot_date":"2005-12","composition":{"2005":192483,"2002":95435,"2004":2503,"2003":1868}},{"snapshot_date":"2006-03","composition":{"2005":147165,"2006":24364,"2002":1852,"2004":152,"2003":357}},{"snapshot_date":"2006-06","composition":{"2006":42885,"2005":144356,"2002":1837,"2004":148,"2003":355}},{"snapshot_date":"2006-09","composition":{"2005":135193,"2006":106620,"2002":1809,"2004":145,"2003":339}},{"snapshot_date":"2006-12","composition":{"2006":82177,"2005":134843,"2002":1803,"2004":144,"2003":339}},{"snapshot_date":"2007-03","composition":{"2006":76164,"2007":13620,"2005":134590,"2002":1749,"2004":144,"2003":333}},{"snapshot_date":"2007-06","composition":{"2006":74427,"2007":21742,"2005":133132,"2002":1747,"2004":144,"2003":333}},{"snapshot_date":"2007-09","composition":{"2006":61136,"2005":124879,"2007":49906,"2002":1742,"2004":144,"2003":333}},{"snapshot_date":"2007-12","composition":{"2007":58304,"2006":60016,"2005":123821,"2002":1739,"2004":144,"2003":332}},{"snapshot_date":"2008-03","composition":{"2006":58723,"2007":61831,"2005":122014,"2008":11648,"2002":1736,"2004":144,"2003":328}},{"snapshot_date":"2008-06","composition":{"2007":54121,"2006":52922,"2008":35857,"2005":120599,"2002":1602,"2004":141,"2003":316}},{"snapshot_date":"2008-09","composition":{"2008":136007,"2006":33511,"2007":46987,"2005":119750,"2002":1353,"2004":138,"2003":281}},{"snapshot_date":"2008-12","composition":{"2007":45694,"2006":33032,"2005":118888,"2008":162098,"2002":1352,"2004":138,"2003":281}},{"snapshot_date":"2009-03","composition":{"2005":118010,"2006":31300,"2007":40897,"2009":45419,"2008":155221,"2002":1351,"2004":138,"2003":278}},{"snapshot_date":"2009-06","composition":{"2007":30084,"2006":26011,"2009":87758,"2005":116213,"2008":148517,"2002":1346,"2004":138,"2003":278}},{"snapshot_date":"2009-09","composition":{"2008":146944,"2009":100827,"2007":29821,"2006":25778,"2005":116065,"2002":1158,"2004":136,"2003":278}},{"snapshot_date":"2009-12","composition":{"2005":113855,"2009":145000,"2007":28736,"2006":25011,"2008":143661,"2002":1158,"2004":136,"2003":273}},{"snapshot_date":"2010-03","composition":{"2009":136927,"2006":24474,"2008":142180,"2005":111850,"2007":28449,"2010":17329,"2002":1064,"2004":123,"2003":259}},{"snapshot_date":"2010-06","composition":{"2007":28023,"2005":111719,"2009":136179,"2008":141950,"2006":23646,"2010":23093,"2002":1057,"2004":123,"2003":259}},{"snapshot_date":"2010-09","composition":{"2010":28562,"2006":23589,"2007":25037,"2008":141483,"2005":111669,"2009":135214,"2002":1055,"2004":123,"2003":259}},{"snapshot_date":"2010-12","composition":{"2008":140070,"2009":125151,"2006":23469,"2010":43152,"2005":111642,"2007":24957,"2002":1046,"2004":123,"2003":259}},{"snapshot_date":"2011-03","composition":{"2010":49445,"2009":118996,"2006":23132,"2007":23654,"2005":111319,"2008":138780,"2011":38997,"2002":1039,"2004":123,"2003":257}},{"snapshot_date":"2011-06","composition":{"2010":48692,"2008":138338,"2011":58195,"2009":115191,"2006":22996,"2005":111133,"2007":23524,"2002":995,"2004":122,"2003":257}},{"snapshot_date":"2011-09","composition":{"2011":88031,"2010":46487,"2008":137097,"2009":112417,"2007":22880,"2006":22699,"2005":110990,"2002":995,"2004":121,"2003":257}},{"snapshot_date":"2011-12","composition":{"2010":46148,"2011":92965,"2008":137041,"2009":110755,"2007":22841,"2005":110983,"2006":22541,"2002":995,"2004":121,"2003":257}},{"snapshot_date":"2012-03","composition":{"2010":42510,"2011":95632,"2008":136391,"2009":108206,"2007":22505,"2006":22193,"2005":110401,"2012":12933,"2002":995,"2004":121,"2003":257}},{"snapshot_date":"2012-06","composition":{"2012":20487,"2010":41767,"2011":82718,"2005":110335,"2006":21976,"2007":22419,"2009":107297,"2008":136224,"2002":990,"2004":121,"2003":255}},{"snapshot_date":"2012-09","composition":{"2011":79589,"2007":22074,"2006":21736,"2010":41149,"2012":30421,"2008":135866,"2009":105324,"2005":110282,"2002":990,"2004":121,"2003":255}},{"snapshot_date":"2012-12","composition":{"2011":79086,"2010":40909,"2012":32983,"2008":135824,"2009":104919,"2006":21734,"2007":22062,"2005":110248,"2002":990,"2004":121,"2003":255}},{"snapshot_date":"2013-03","composition":{"2012":29512,"2010":40751,"2011":77641,"2013":7097,"2006":21520,"2009":103144,"2008":134237,"2005":109882,"2007":21603,"2002":989,"2004":121,"2003":254}},{"snapshot_date":"2013-06","composition":{"2012":183454,"2010":39837,"2013":24040,"2011":77317,"2008":133562,"2009":101929,"2005":35090,"2006":21080,"2007":21351,"2002":987,"2004":120,"2003":253}},{"snapshot_date":"2013-09","composition":{"2012":181910,"2010":37648,"2013":44136,"2011":75123,"2008":127205,"2009":97164,"2006":14587,"2005":33008,"2007":20078,"2002":711,"2004":78,"2003":252}},{"snapshot_date":"2013-12","composition":{"2012":180411,"2013":46300,"2010":37466,"2011":74810,"2006":14451,"2009":96743,"2005":32818,"2007":19912,"2008":126806,"2002":709,"2004":77,"2003":252}},{"snapshot_date":"2014-03","composition":{"2010":36748,"2012":178530,"2013":43606,"2011":73040,"2014":18240,"2005":30273,"2006":13029,"2007":19769,"2008":45858,"2009":94894,"2002":696,"2004":77,"2003":252}},{"snapshot_date":"2014-06","composition":{"2012":171754,"2014":13714,"2013":41708,"2011":70672,"2010":36188,"2009":87451,"2005":30161,"2008":45113,"2007":18289,"2006":12022,"2002":695,"2004":77,"2003":252}},{"snapshot_date":"2014-09","composition":{"2012":171112,"2014":22362,"2013":40712,"2010":35603,"2011":69888,"2008":44758,"2009":86034,"2006":11875,"2005":29302,"2007":18123,"2002":635,"2004":60,"2003":252}},{"snapshot_date":"2014-12","composition":{"2013":40593,"2010":35489,"2014":25570,"2012":170931,"2011":69766,"2008":44138,"2009":85660,"2005":29175,"2006":11771,"2007":17939,"2002":634,"2004":60,"2003":252}},{"snapshot_date":"2015-03","composition":{"2011":69486,"2013":39957,"2010":35074,"2015":6679,"2012":170636,"2014":26701,"2008":43679,"2009":84814,"2005":29095,"2006":11512,"2007":17877,"2002":634,"2004":60,"2003":252}},{"snapshot_date":"2015-06","composition":{"2012":170329,"2015":12670,"2014":27726,"2013":39699,"2010":34951,"2011":68961,"2006":11386,"2009":84282,"2005":29020,"2008":43249,"2007":17747,"2002":631,"2004":60,"2003":252}},{"snapshot_date":"2015-09","composition":{"2010":34469,"2014":27200,"2015":29669,"2012":169803,"2013":37171,"2011":67345,"2008":41734,"2009":83642,"2006":10984,"2005":26685,"2007":16829,"2002":630,"2004":56,"2003":234}},{"snapshot_date":"2015-12","composition":{"2015":35389,"2013":37010,"2010":34274,"2014":26998,"2012":169642,"2011":66650,"2006":10946,"2008":41411,"2009":83082,"2005":26566,"2007":16737,"2002":629,"2004":56,"2003":234}},{"snapshot_date":"2016-03","composition":{"2013":36586,"2012":169383,"2015":36532,"2016":7776,"2010":34125,"2014":26521,"2008":41062,"2009":82599,"2011":65855,"2005":26355,"2007":16480,"2006":10683,"2002":629,"2004":56,"2003":234}},{"snapshot_date":"2016-06","composition":{"2013":36522,"2015":36608,"2016":10759,"2010":34002,"2014":26371,"2012":169365,"2011":65773,"2008":40860,"2009":82399,"2006":10650,"2005":26319,"2007":16317,"2002":623,"2004":56,"2003":234}},{"snapshot_date":"2016-09","composition":{"2015":36237,"2012":169253,"2016":15735,"2010":33913,"2014":26125,"2013":36265,"2011":65666,"2008":40740,"2009":82280,"2006":10609,"2005":26245,"2007":16285,"2002":623,"2004":56,"2003":234}},{"snapshot_date":"2016-12","composition":{"2012":169155,"2015":36071,"2016":21595,"2014":25757,"2013":35985,"2010":33535,"2011":65332,"2005":26216,"2009":82159,"2008":40607,"2006":10541,"2007":16223,"2002":622,"2004":56,"2003":233}},{"snapshot_date":"2017-03","composition":{"2014":25576,"2017":110922,"2015":35648,"2012":62863,"2016":39453,"2013":35176,"2010":33095,"2011":65019,"2008":39631,"2009":68413,"2005":25919,"2007":16089,"2006":10382,"2002":570,"2004":51,"2003":233}},{"snapshot_date":"2017-06","composition":{"2014":24843,"2017":120751,"2010":32885,"2015":35392,"2016":38979,"2012":62775,"2013":33867,"2011":64813,"2006":10225,"2008":39305,"2005":17793,"2009":66773,"2007":15934,"2002":563,"2004":49,"2003":233}},{"snapshot_date":"2017-09","composition":{"2014":24395,"2017":133110,"2015":34827,"2013":33024,"2010":32136,"2016":38009,"2012":62537,"2011":63614,"2008":38254,"2009":65072,"2005":17186,"2006":10061,"2007":15636,"2002":559,"2004":49,"2003":233}},{"snapshot_date":"2017-12","composition":{"2017":149940,"2015":34262,"2016":37400,"2014":24265,"2013":32565,"2012":62341,"2010":31890,"2011":63048,"2006":9933,"2008":37707,"2005":17051,"2009":64466,"2007":15468,"2002":559,"2004":49,"2003":233}},{"snapshot_date":"2018-03","composition":{"2018":5510,"2015":33944,"2017":150500,"2016":36999,"2012":61939,"2014":22677,"2013":32357,"2010":31760,"2011":62549,"2008":37535,"2009":64198,"2006":9853,"2005":16919,"2007":15411,"2002":559,"2004":49,"2003":233}},{"snapshot_date":"2018-06","composition":{"2018":19708,"2017":150670,"2015":33690,"2016":36365,"2012":61569,"2014":22274,"2010":31401,"2013":31673,"2011":61256,"2006":9774,"2008":36911,"2009":63381,"2005":16840,"2007":15269,"2002":550,"2004":49,"2003":232}},{"snapshot_date":"2018-09","composition":{"2018":30527,"2014":21992,"2017":150375,"2015":33569,"2016":36174,"2012":61426,"2010":31239,"2013":31339,"2011":60402,"2005":16558,"2008":36478,"2007":15145,"2006":9397,"2009":62752,"2002":543,"2004":49,"2003":232}},{"snapshot_date":"2018-12","composition":{"2018":48265,"2017":149585,"2015":32778,"2016":35741,"2014":21809,"2013":31020,"2012":61190,"2006":9063,"2008":35767,"2009":60872,"2005":14910,"2007":15047,"2010":31055,"2011":59340,"2002":539,"2004":49,"2003":232}},{"snapshot_date":"2019-03","composition":{"2017":149531,"2015":32514,"2018":49111,"2016":35564,"2014":21280,"2019":9272,"2012":60851,"2010":30642,"2013":30072,"2011":58341,"2008":35532,"2009":60367,"2006":9016,"2005":14835,"2007":15001,"2002":539,"2004":49,"2003":232}},{"snapshot_date":"2019-06","composition":{"2017":149695,"2015":32943,"2018":41877,"2016":36017,"2014":21938,"2013":31076,"2010":31147,"2012":61227,"2011":59492,"2006":9065,"2005":16430,"2009":61872,"2008":36237,"2007":15084,"2002":539,"2004":49,"2019":64,"2003":232}},{"snapshot_date":"2019-09","composition":{"2018":66238,"2019":47674,"2014":20457,"2017":149155,"2013":29628,"2012":60316,"2015":31132,"2016":34764,"2010":30023,"2011":57507,"2006":8623,"2005":12783,"2009":57563,"2008":33249,"2007":14521,"2002":538,"2004":49,"2003":231}},{"snapshot_date":"2019-12","composition":{"2018":65394,"2019":56820,"2015":30831,"2014":19795,"2017":148850,"2008":32874,"2016":34561,"2009":57304,"2012":60140,"2013":29363,"2010":29927,"2011":57286,"2005":12717,"2007":14497,"2006":8557,"2002":537,"2004":49,"2003":231}},{"snapshot_date":"2020-03","composition":{"2018":64016,"2019":57180,"2020":24171,"2014":19448,"2017":147161,"2015":30240,"2013":28115,"2012":59428,"2016":33939,"2008":32306,"2009":55012,"2010":27781,"2011":55546,"2006":8293,"2005":12409,"2007":14306,"2002":531,"2004":49,"2003":230}},{"snapshot_date":"2020-06","composition":{"2019":56788,"2018":63130,"2020":48891,"2014":19034,"2017":147034,"2008":32160,"2016":33750,"2015":30033,"2009":54186,"2013":27979,"2010":27613,"2012":59098,"2011":54685,"2005":12251,"2007":14291,"2006":8235,"2002":531,"2004":49,"2003":230}},{"snapshot_date":"2020-09","composition":{"2019":55422,"2020":77573,"2018":62241,"2015":29338,"2014":18764,"2017":145552,"2010":19266,"2016":33581,"2012":58501,"2013":27478,"2011":51167,"2006":8067,"2008":30427,"2009":47014,"2005":12176,"2007":14242,"2002":527,"2004":49,"2003":230}},{"snapshot_date":"2020-12","composition":{"2018":61019,"2019":54317,"2020":114522,"2014":18594,"2017":145129,"2015":28817,"2010":19070,"2016":33480,"2012":58445,"2013":26876,"2011":50577,"2008":30008,"2009":46735,"2006":8047,"2005":12142,"2007":14182,"2002":526,"2004":49,"2003":230}},{"snapshot_date":"2021-03","composition":{"2018":61349,"2020":104729,"2019":54845,"2014":18621,"2017":145434,"2015":29101,"2012":58457,"2016":33526,"2010":19104,"2013":27231,"2011":50614,"2008":30056,"2009":46855,"2006":8051,"2005":12145,"2007":14189,"2021":2222,"2002":527,"2004":49,"2003":230}},{"snapshot_date":"2021-06","composition":{"2020":112028,"2021":45055,"2018":59930,"2019":53018,"2015":28456,"2010":18935,"2016":33218,"2012":58314,"2017":144293,"2014":18363,"2013":26466,"2011":47525,"2006":8013,"2008":30260,"2009":46200,"2005":12104,"2007":14141,"2002":524,"2004":49,"2003":230}},{"snapshot_date":"2021-09","composition":{"2018":59052,"2021":97402,"2019":52650,"2020":109267,"2015":27816,"2010":18656,"2016":33061,"2012":58200,"2017":143713,"2014":17505,"2013":25955,"2011":46683,"2006":7931,"2008":29288,"2009":45809,"2005":11659,"2007":13859,"2002":519,"2004":48,"2003":230}},{"snapshot_date":"2021-12","composition":{"2020":106648,"2021":121834,"2018":58489,"2019":52144,"2015":27360,"2010":17939,"2016":32977,"2012":57388,"2017":143312,"2014":16983,"2013":25758,"2011":46229,"2008":29088,"2006":7907,"2005":11544,"2009":45709,"2007":13832,"2002":519,"2004":48,"2003":230}},{"snapshot_date":"2022-03","composition":{"2021":125502,"2018":58156,"2019":51279,"2020":105366,"2022":17765,"2015":26951,"2012":56311,"2016":32844,"2013":25031,"2010":17808,"2017":143069,"2014":16635,"2011":46017,"2008":28815,"2009":45556,"2005":11537,"2007":13822,"2006":7895,"2002":519,"2004":48,"2003":230}},{"snapshot_date":"2022-06","composition":{"2018":57807,"2021":126196,"2020":104264,"2019":50945,"2022":37665,"2015":26535,"2010":17599,"2016":32719,"2012":55823,"2017":142586,"2014":16534,"2013":24376,"2011":45697,"2008":28642,"2009":44994,"2005":11438,"2006":7797,"2007":13733,"2002":516,"2004":48,"2003":229}},{"snapshot_date":"2022-09","composition":{"2020":103997,"2018":57726,"2021":125728,"2022":42874,"2019":50800,"2015":26468,"2010":17534,"2016":32655,"2012":55811,"2017":142510,"2014":16422,"2013":24318,"2011":45687,"2006":7794,"2008":28526,"2009":44517,"2005":11438,"2007":13668,"2002":516,"2004":48,"2003":229}},{"snapshot_date":"2022-12","composition":{"2022":55955,"2018":57356,"2019":50605,"2021":124787,"2020":103290,"2015":25906,"2010":17490,"2016":32562,"2012":55746,"2017":142114,"2014":16365,"2013":23784,"2011":45494,"2006":7761,"2005":11405,"2009":44247,"2008":28450,"2007":13577,"2002":516,"2004":48,"2003":229}},{"snapshot_date":"2023-03","composition":{"2022":59168,"2023":10358,"2021":120524,"2018":56770,"2020":100976,"2019":49742,"2015":25710,"2008":28012,"2016":32315,"2009":44088,"2014":16282,"2017":140354,"2010":17341,"2012":55648,"2013":23482,"2011":45145,"2006":7683,"2005":11389,"2007":13558,"2002":516,"2004":48,"2003":228}},{"snapshot_date":"2023-06","composition":{"2022":58437,"2023":34207,"2021":107984,"2020":100173,"2018":56373,"2019":49163,"2015":25495,"2010":16797,"2016":32250,"2012":55502,"2017":139945,"2014":16125,"2013":23318,"2011":44806,"2008":27784,"2009":43728,"2006":7209,"2005":11334,"2007":13493,"2002":516,"2004":48,"2003":227}},{"snapshot_date":"2023-09","composition":{"2022":55700,"2023":62491,"2021":100401,"2020":94581,"2018":55435,"2019":47753,"2015":24632,"2016":30433,"2017":138546,"2012":55265,"2010":16400,"2014":15672,"2013":22552,"2011":44120,"2008":26921,"2009":41512,"2006":6928,"2005":11158,"2007":13056,"2004":48,"2003":220,"2002":509}},{"snapshot_date":"2023-12","composition":{"2022":54599,"2023":91904,"2021":98879,"2018":53945,"2020":93516,"2019":47067,"2015":24156,"2010":15736,"2016":30177,"2012":54822,"2017":137828,"2014":15179,"2013":21937,"2011":43821,"2006":6464,"2008":26233,"2009":40323,"2005":10910,"2007":12624,"2004":31,"2003":217,"2002":486}},{"snapshot_date":"2024-03","composition":{"2018":50761,"2019":46690,"2022":52839,"2023":83911,"2021":93662,"2020":92785,"2024":30118,"2015":24044,"2010":15661,"2016":30014,"2012":54601,"2017":137644,"2014":15119,"2013":21678,"2011":43446,"2006":6389,"2008":26095,"2005":10883,"2009":39081,"2007":12600,"2004":31,"2003":217,"2002":485}},{"snapshot_date":"2024-06","composition":{"2023":82879,"2024":39429,"2022":52334,"2021":93118,"2018":50598,"2020":92278,"2019":46371,"2015":23986,"2010":15623,"2016":29981,"2012":54472,"2017":137433,"2014":15091,"2013":21640,"2011":43348,"2005":10869,"2008":26022,"2007":12593,"2009":39036,"2006":6379,"2004":31,"2003":217,"2002":485}},{"snapshot_date":"2024-09","composition":{"2023":81060,"2024":58495,"2022":51245,"2021":91299,"2018":49868,"2015":23851,"2019":45868,"2020":91654,"2010":15329,"2016":29872,"2012":54407,"2017":137201,"2014":14959,"2013":21558,"2011":42777,"2006":6362,"2008":25874,"2009":38030,"2005":10829,"2007":12568,"2004":31,"2003":217,"2002":485}},{"snapshot_date":"2024-12","composition":{"2022":50651,"2023":79580,"2020":90805,"2021":89441,"2024":79718,"2018":49399,"2019":45459,"2015":23478,"2010":15070,"2016":29631,"2012":54260,"2017":136825,"2014":14558,"2013":20957,"2011":40920,"2006":6288,"2008":25636,"2009":37605,"2005":10787,"2007":12206,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-01","composition":{"2021":89243,"2022":50615,"2023":79451,"2018":49378,"2024":80020,"2020":90749,"2015":23478,"2019":45451,"2025":2383,"2010":15065,"2016":29621,"2012":54234,"2017":136711,"2014":14557,"2013":20932,"2011":40903,"2008":25635,"2009":37562,"2006":6288,"2005":10787,"2007":12206,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-02","composition":{"2022":50496,"2023":79170,"2021":89103,"2025":6210,"2024":79734,"2018":49255,"2020":90244,"2019":45295,"2015":23474,"2012":54148,"2016":29571,"2017":136630,"2010":15058,"2014":14553,"2013":20915,"2011":40815,"2006":6278,"2005":10787,"2008":25633,"2007":12204,"2009":37544,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-03","composition":{"2022":49804,"2023":78817,"2021":88041,"2024":78977,"2025":12625,"2018":49216,"2019":45287,"2020":90074,"2015":23424,"2010":15051,"2016":29570,"2012":54148,"2017":136627,"2014":14545,"2013":20864,"2011":40815,"2006":6278,"2008":25623,"2009":37543,"2005":10787,"2007":11959,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-04","composition":{"2018":49092,"2023":77780,"2025":19503,"2024":77274,"2021":86816,"2022":49391,"2020":89794,"2019":45201,"2015":23102,"2010":15012,"2016":29497,"2012":54125,"2017":136524,"2014":14525,"2013":20769,"2011":40682,"2008":25591,"2009":37512,"2005":10762,"2007":11951,"2006":6270,"2004":23,"2003":211,"2002":464}},{"snapshot_date":"2025-05","composition":{"2022":49089,"2023":77083,"2018":48737,"2020":89322,"2021":85760,"2025":27815,"2024":76096,"2019":45009,"2015":22923,"2012":54096,"2016":29405,"2017":136356,"2008":25546,"2009":37291,"2014":14458,"2010":14979,"2013":20628,"2011":40637,"2006":6254,"2005":10703,"2007":11866,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-06","composition":{"2022":49033,"2023":76925,"2021":85569,"2025":32977,"2020":89202,"2024":75818,"2018":48697,"2019":44978,"2015":22919,"2010":14972,"2016":29396,"2012":54070,"2017":136354,"2014":14454,"2013":20607,"2011":40621,"2005":10703,"2008":25546,"2007":11865,"2006":6254,"2009":37289,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-07","composition":{"2022":48964,"2023":76778,"2021":85458,"2025":35930,"2020":89056,"2018":48554,"2019":44888,"2024":75473,"2015":22890,"2010":14950,"2016":29238,"2012":54062,"2017":136300,"2014":14447,"2013":20592,"2011":40589,"2008":25513,"2009":37219,"2006":6254,"2005":10699,"2007":11858,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-08","composition":{"2021":84948,"2025":39378,"2022":48745,"2023":76487,"2020":88961,"2024":75168,"2018":48540,"2019":44846,"2015":22809,"2012":54022,"2016":29174,"2017":136261,"2010":14906,"2014":14425,"2013":20457,"2011":40518,"2008":25462,"2009":36955,"2006":6254,"2005":10699,"2007":11844,"2003":211,"2004":22,"2002":450}},{"snapshot_date":"2025-09","composition":{"2022":48340,"2023":76041,"2020":88796,"2021":81876,"2025":44888,"2024":74576,"2018":48164,"2015":22727,"2019":44345,"2010":14874,"2016":28894,"2012":53973,"2017":136220,"2014":14395,"2013":20405,"2011":40432,"2008":25414,"2009":36912,"2006":6223,"2005":10693,"2007":11841,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-10","composition":{"2022":48192,"2023":75293,"2021":80321,"2025":53113,"2018":47992,"2020":88588,"2024":72865,"2019":44311,"2015":22561,"2010":14854,"2016":28862,"2012":53957,"2017":136093,"2014":14375,"2013":20347,"2011":40375,"2006":6195,"2008":25189,"2005":10677,"2009":36813,"2007":11833,"2004":17,"2002":442,"2003":46}},{"snapshot_date":"2025-11","composition":{"2021":79605,"2025":59992,"2022":47943,"2023":74782,"2024":72354,"2020":88240,"2018":47831,"2015":22537,"2019":44191,"2010":14843,"2016":28752,"2012":53950,"2017":135910,"2014":14359,"2013":20274,"2011":40175,"2008":25102,"2009":36633,"2006":6059,"2005":10673,"2007":11780,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2025-12","composition":{"2021":77624,"2025":66882,"2022":47371,"2023":72787,"2018":47273,"2020":83371,"2024":70724,"2019":42674,"2015":21873,"2012":53465,"2016":28263,"2017":134788,"2010":14549,"2014":13988,"2013":19504,"2011":39948,"2008":24669,"2009":34785,"2006":4411,"2005":6078,"2007":9963,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-01","composition":{"2023":72372,"2025":66534,"2024":70327,"2022":47318,"2021":76902,"2018":47222,"2020":83204,"2019":42563,"2026":5958,"2015":21829,"2010":14537,"2016":28262,"2012":53457,"2017":134734,"2014":13972,"2013":19463,"2011":39886,"2006":4399,"2008":24649,"2009":34693,"2005":6067,"2007":9953,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-02","composition":{"2018":46641,"2022":47278,"2023":72221,"2021":76791,"2025":66399,"2020":82827,"2024":69905,"2015":21821,"2019":42547,"2026":7958,"2012":53453,"2016":28172,"2017":134717,"2008":24622,"2009":34684,"2014":13935,"2010":14529,"2013":19456,"2011":39882,"2006":4398,"2005":6064,"2007":9946,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-03","composition":{"2023":72001,"2025":66092,"2024":69743,"2022":47221,"2021":76469,"2018":46580,"2020":82565,"2019":42499,"2015":21647,"2026":12114,"2010":14494,"2016":28145,"2012":53430,"2017":134676,"2014":13906,"2013":19444,"2011":39765,"2008":24608,"2009":34566,"2006":4397,"2005":6057,"2007":9938,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-04","composition":{"2021":76438,"2025":65899,"2022":47184,"2023":71932,"2024":69683,"2018":46568,"2026":13802,"2020":82535,"2019":42499,"2015":21641,"2012":53424,"2016":28137,"2017":134665,"2006":4396,"2010":14491,"2014":13904,"2013":19432,"2011":39759,"2008":24608,"2005":6057,"2009":34553,"2007":9937,"2004":17,"2002":439,"2003":46}}],"fossils":{"genesis":{"timestamp":1008690310,"file":"scipy_distutils/__init__.py","content":"\"\"\"scipy_distutils","year":"2001","commit":"f1a2d63","line":1},"survivor":{"timestamp":1008690310,"file":"scipy_distutils/__init__.py","content":"\"\"\"scipy_distutils","year":"2001","commit":"f1a2d63","line":1}}} \ No newline at end of file +{"snapshots":[{"snapshot_date":"2001-12","composition":{"2001":1865}},{"snapshot_date":"2002-03","composition":{"2002":94339,"2001":1472}},{"snapshot_date":"2002-06","composition":{"2002":102869,"2001":1179}},{"snapshot_date":"2002-09","composition":{"2002":130360,"2001":1167}},{"snapshot_date":"2002-12","composition":{"2002":132966,"2001":1130}},{"snapshot_date":"2003-03","composition":{"2003":2305,"2002":132607,"2001":1052}},{"snapshot_date":"2003-06","composition":{"2002":132569,"2003":2688,"2001":1047}},{"snapshot_date":"2003-09","composition":{"2003":3793,"2002":132461,"2001":1036}},{"snapshot_date":"2003-12","composition":{"2003":5328,"2002":131017,"2001":1009}},{"snapshot_date":"2004-03","composition":{"2003":3964,"2002":129547,"2004":5960,"2001":449}},{"snapshot_date":"2004-06","composition":{"2004":9689,"2002":129500,"2003":3916,"2001":443}},{"snapshot_date":"2004-09","composition":{"2003":3868,"2002":128923,"2004":10562,"2001":443}},{"snapshot_date":"2004-12","composition":{"2004":13272,"2002":128551,"2003":3680,"2001":437}},{"snapshot_date":"2005-03","composition":{"2002":128546,"2004":13125,"2003":3676,"2005":352,"2001":437}},{"snapshot_date":"2005-06","composition":{"2004":13106,"2005":1801,"2002":128527,"2003":3655,"2001":437}},{"snapshot_date":"2005-09","composition":{"2005":150609,"2002":120948,"2004":2743,"2003":2178}},{"snapshot_date":"2005-12","composition":{"2005":192483,"2002":95435,"2004":2503,"2003":1868}},{"snapshot_date":"2006-03","composition":{"2005":147165,"2006":24364,"2002":1852,"2004":152,"2003":357}},{"snapshot_date":"2006-06","composition":{"2006":42885,"2005":144356,"2002":1837,"2004":148,"2003":355}},{"snapshot_date":"2006-09","composition":{"2005":135193,"2006":106620,"2002":1809,"2004":145,"2003":339}},{"snapshot_date":"2006-12","composition":{"2006":82177,"2005":134843,"2002":1803,"2004":144,"2003":339}},{"snapshot_date":"2007-03","composition":{"2006":76164,"2007":13620,"2005":134590,"2002":1749,"2004":144,"2003":333}},{"snapshot_date":"2007-06","composition":{"2006":74427,"2007":21742,"2005":133132,"2002":1747,"2004":144,"2003":333}},{"snapshot_date":"2007-09","composition":{"2006":61136,"2005":124879,"2007":49906,"2002":1742,"2004":144,"2003":333}},{"snapshot_date":"2007-12","composition":{"2007":58304,"2006":60016,"2005":123821,"2002":1739,"2004":144,"2003":332}},{"snapshot_date":"2008-03","composition":{"2006":58723,"2007":61831,"2005":122014,"2008":11648,"2002":1736,"2004":144,"2003":328}},{"snapshot_date":"2008-06","composition":{"2007":54121,"2006":52922,"2008":35857,"2005":120599,"2002":1602,"2004":141,"2003":316}},{"snapshot_date":"2008-09","composition":{"2008":136007,"2006":33511,"2007":46987,"2005":119750,"2002":1353,"2004":138,"2003":281}},{"snapshot_date":"2008-12","composition":{"2007":45694,"2006":33032,"2005":118888,"2008":162098,"2002":1352,"2004":138,"2003":281}},{"snapshot_date":"2009-03","composition":{"2005":118010,"2006":31300,"2007":40897,"2009":45419,"2008":155221,"2002":1351,"2004":138,"2003":278}},{"snapshot_date":"2009-06","composition":{"2007":30084,"2006":26011,"2009":87758,"2005":116213,"2008":148517,"2002":1346,"2004":138,"2003":278}},{"snapshot_date":"2009-09","composition":{"2008":146944,"2009":100827,"2007":29821,"2006":25778,"2005":116065,"2002":1158,"2004":136,"2003":278}},{"snapshot_date":"2009-12","composition":{"2005":113855,"2009":145000,"2007":28736,"2006":25011,"2008":143661,"2002":1158,"2004":136,"2003":273}},{"snapshot_date":"2010-03","composition":{"2009":136927,"2006":24474,"2008":142180,"2005":111850,"2007":28449,"2010":17329,"2002":1064,"2004":123,"2003":259}},{"snapshot_date":"2010-06","composition":{"2007":28023,"2005":111719,"2009":136179,"2008":141950,"2006":23646,"2010":23093,"2002":1057,"2004":123,"2003":259}},{"snapshot_date":"2010-09","composition":{"2010":28562,"2006":23589,"2007":25037,"2008":141483,"2005":111669,"2009":135214,"2002":1055,"2004":123,"2003":259}},{"snapshot_date":"2010-12","composition":{"2008":140070,"2009":125151,"2006":23469,"2010":43152,"2005":111642,"2007":24957,"2002":1046,"2004":123,"2003":259}},{"snapshot_date":"2011-03","composition":{"2010":49445,"2009":118996,"2006":23132,"2007":23654,"2005":111319,"2008":138780,"2011":38997,"2002":1039,"2004":123,"2003":257}},{"snapshot_date":"2011-06","composition":{"2010":48692,"2008":138338,"2011":58195,"2009":115191,"2006":22996,"2005":111133,"2007":23524,"2002":995,"2004":122,"2003":257}},{"snapshot_date":"2011-09","composition":{"2011":88031,"2010":46487,"2008":137097,"2009":112417,"2007":22880,"2006":22699,"2005":110990,"2002":995,"2004":121,"2003":257}},{"snapshot_date":"2011-12","composition":{"2010":46148,"2011":92965,"2008":137041,"2009":110755,"2007":22841,"2005":110983,"2006":22541,"2002":995,"2004":121,"2003":257}},{"snapshot_date":"2012-03","composition":{"2010":42510,"2011":95632,"2008":136391,"2009":108206,"2007":22505,"2006":22193,"2005":110401,"2012":12933,"2002":995,"2004":121,"2003":257}},{"snapshot_date":"2012-06","composition":{"2012":20487,"2010":41767,"2011":82718,"2005":110335,"2006":21976,"2007":22419,"2009":107297,"2008":136224,"2002":990,"2004":121,"2003":255}},{"snapshot_date":"2012-09","composition":{"2011":79589,"2007":22074,"2006":21736,"2010":41149,"2012":30421,"2008":135866,"2009":105324,"2005":110282,"2002":990,"2004":121,"2003":255}},{"snapshot_date":"2012-12","composition":{"2011":79086,"2010":40909,"2012":32983,"2008":135824,"2009":104919,"2006":21734,"2007":22062,"2005":110248,"2002":990,"2004":121,"2003":255}},{"snapshot_date":"2013-03","composition":{"2012":29512,"2010":40751,"2011":77641,"2013":7097,"2006":21520,"2009":103144,"2008":134237,"2005":109882,"2007":21603,"2002":989,"2004":121,"2003":254}},{"snapshot_date":"2013-06","composition":{"2012":183454,"2010":39837,"2013":24040,"2011":77317,"2008":133562,"2009":101929,"2005":35090,"2006":21080,"2007":21351,"2002":987,"2004":120,"2003":253}},{"snapshot_date":"2013-09","composition":{"2012":181910,"2010":37648,"2013":44136,"2011":75123,"2008":127205,"2009":97164,"2006":14587,"2005":33008,"2007":20078,"2002":711,"2004":78,"2003":252}},{"snapshot_date":"2013-12","composition":{"2012":180411,"2013":46300,"2010":37466,"2011":74810,"2006":14451,"2009":96743,"2005":32818,"2007":19912,"2008":126806,"2002":709,"2004":77,"2003":252}},{"snapshot_date":"2014-03","composition":{"2010":36748,"2012":178530,"2013":43606,"2011":73040,"2014":18240,"2005":30273,"2006":13029,"2007":19769,"2008":45858,"2009":94894,"2002":696,"2004":77,"2003":252}},{"snapshot_date":"2014-06","composition":{"2012":171754,"2014":13714,"2013":41708,"2011":70672,"2010":36188,"2009":87451,"2005":30161,"2008":45113,"2007":18289,"2006":12022,"2002":695,"2004":77,"2003":252}},{"snapshot_date":"2014-09","composition":{"2012":171112,"2014":22362,"2013":40712,"2010":35603,"2011":69888,"2008":44758,"2009":86034,"2006":11875,"2005":29302,"2007":18123,"2002":635,"2004":60,"2003":252}},{"snapshot_date":"2014-12","composition":{"2013":40593,"2010":35489,"2014":25570,"2012":170931,"2011":69766,"2008":44138,"2009":85660,"2005":29175,"2006":11771,"2007":17939,"2002":634,"2004":60,"2003":252}},{"snapshot_date":"2015-03","composition":{"2011":69486,"2013":39957,"2010":35074,"2015":6679,"2012":170636,"2014":26701,"2008":43679,"2009":84814,"2005":29095,"2006":11512,"2007":17877,"2002":634,"2004":60,"2003":252}},{"snapshot_date":"2015-06","composition":{"2012":170329,"2015":12670,"2014":27726,"2013":39699,"2010":34951,"2011":68961,"2006":11386,"2009":84282,"2005":29020,"2008":43249,"2007":17747,"2002":631,"2004":60,"2003":252}},{"snapshot_date":"2015-09","composition":{"2010":34469,"2014":27200,"2015":29669,"2012":169803,"2013":37171,"2011":67345,"2008":41734,"2009":83642,"2006":10984,"2005":26685,"2007":16829,"2002":630,"2004":56,"2003":234}},{"snapshot_date":"2015-12","composition":{"2015":35389,"2013":37010,"2010":34274,"2014":26998,"2012":169642,"2011":66650,"2006":10946,"2008":41411,"2009":83082,"2005":26566,"2007":16737,"2002":629,"2004":56,"2003":234}},{"snapshot_date":"2016-03","composition":{"2013":36586,"2012":169383,"2015":36532,"2016":7776,"2010":34125,"2014":26521,"2008":41062,"2009":82599,"2011":65855,"2005":26355,"2007":16480,"2006":10683,"2002":629,"2004":56,"2003":234}},{"snapshot_date":"2016-06","composition":{"2013":36522,"2015":36608,"2016":10759,"2010":34002,"2014":26371,"2012":169365,"2011":65773,"2008":40860,"2009":82399,"2006":10650,"2005":26319,"2007":16317,"2002":623,"2004":56,"2003":234}},{"snapshot_date":"2016-09","composition":{"2015":36237,"2012":169253,"2016":15735,"2010":33913,"2014":26125,"2013":36265,"2011":65666,"2008":40740,"2009":82280,"2006":10609,"2005":26245,"2007":16285,"2002":623,"2004":56,"2003":234}},{"snapshot_date":"2016-12","composition":{"2012":169155,"2015":36071,"2016":21595,"2014":25757,"2013":35985,"2010":33535,"2011":65332,"2005":26216,"2009":82159,"2008":40607,"2006":10541,"2007":16223,"2002":622,"2004":56,"2003":233}},{"snapshot_date":"2017-03","composition":{"2014":25576,"2017":110922,"2015":35648,"2012":62863,"2016":39453,"2013":35176,"2010":33095,"2011":65019,"2008":39631,"2009":68413,"2005":25919,"2007":16089,"2006":10382,"2002":570,"2004":51,"2003":233}},{"snapshot_date":"2017-06","composition":{"2014":24843,"2017":120751,"2010":32885,"2015":35392,"2016":38979,"2012":62775,"2013":33867,"2011":64813,"2006":10225,"2008":39305,"2005":17793,"2009":66773,"2007":15934,"2002":563,"2004":49,"2003":233}},{"snapshot_date":"2017-09","composition":{"2014":24395,"2017":133110,"2015":34827,"2013":33024,"2010":32136,"2016":38009,"2012":62537,"2011":63614,"2008":38254,"2009":65072,"2005":17186,"2006":10061,"2007":15636,"2002":559,"2004":49,"2003":233}},{"snapshot_date":"2017-12","composition":{"2017":149940,"2015":34262,"2016":37400,"2014":24265,"2013":32565,"2012":62341,"2010":31890,"2011":63048,"2006":9933,"2008":37707,"2005":17051,"2009":64466,"2007":15468,"2002":559,"2004":49,"2003":233}},{"snapshot_date":"2018-03","composition":{"2018":5510,"2015":33944,"2017":150500,"2016":36999,"2012":61939,"2014":22677,"2013":32357,"2010":31760,"2011":62549,"2008":37535,"2009":64198,"2006":9853,"2005":16919,"2007":15411,"2002":559,"2004":49,"2003":233}},{"snapshot_date":"2018-06","composition":{"2018":19708,"2017":150670,"2015":33690,"2016":36365,"2012":61569,"2014":22274,"2010":31401,"2013":31673,"2011":61256,"2006":9774,"2008":36911,"2009":63381,"2005":16840,"2007":15269,"2002":550,"2004":49,"2003":232}},{"snapshot_date":"2018-09","composition":{"2018":30527,"2014":21992,"2017":150375,"2015":33569,"2016":36174,"2012":61426,"2010":31239,"2013":31339,"2011":60402,"2005":16558,"2008":36478,"2007":15145,"2006":9397,"2009":62752,"2002":543,"2004":49,"2003":232}},{"snapshot_date":"2018-12","composition":{"2018":48265,"2017":149585,"2015":32778,"2016":35741,"2014":21809,"2013":31020,"2012":61190,"2006":9063,"2008":35767,"2009":60872,"2005":14910,"2007":15047,"2010":31055,"2011":59340,"2002":539,"2004":49,"2003":232}},{"snapshot_date":"2019-03","composition":{"2017":149531,"2015":32514,"2018":49111,"2016":35564,"2014":21280,"2019":9272,"2012":60851,"2010":30642,"2013":30072,"2011":58341,"2008":35532,"2009":60367,"2006":9016,"2005":14835,"2007":15001,"2002":539,"2004":49,"2003":232}},{"snapshot_date":"2019-06","composition":{"2017":149695,"2015":32943,"2018":41877,"2016":36017,"2014":21938,"2013":31076,"2010":31147,"2012":61227,"2011":59492,"2006":9065,"2005":16430,"2009":61872,"2008":36237,"2007":15084,"2002":539,"2004":49,"2019":64,"2003":232}},{"snapshot_date":"2019-09","composition":{"2018":66238,"2019":47674,"2014":20457,"2017":149155,"2013":29628,"2012":60316,"2015":31132,"2016":34764,"2010":30023,"2011":57507,"2006":8623,"2005":12783,"2009":57563,"2008":33249,"2007":14521,"2002":538,"2004":49,"2003":231}},{"snapshot_date":"2019-12","composition":{"2018":65394,"2019":56820,"2015":30831,"2014":19795,"2017":148850,"2008":32874,"2016":34561,"2009":57304,"2012":60140,"2013":29363,"2010":29927,"2011":57286,"2005":12717,"2007":14497,"2006":8557,"2002":537,"2004":49,"2003":231}},{"snapshot_date":"2020-03","composition":{"2018":64016,"2019":57180,"2020":24171,"2014":19448,"2017":147161,"2015":30240,"2013":28115,"2012":59428,"2016":33939,"2008":32306,"2009":55012,"2010":27781,"2011":55546,"2006":8293,"2005":12409,"2007":14306,"2002":531,"2004":49,"2003":230}},{"snapshot_date":"2020-06","composition":{"2019":56788,"2018":63130,"2020":48891,"2014":19034,"2017":147034,"2008":32160,"2016":33750,"2015":30033,"2009":54186,"2013":27979,"2010":27613,"2012":59098,"2011":54685,"2005":12251,"2007":14291,"2006":8235,"2002":531,"2004":49,"2003":230}},{"snapshot_date":"2020-09","composition":{"2019":55422,"2020":77573,"2018":62241,"2015":29338,"2014":18764,"2017":145552,"2010":19266,"2016":33581,"2012":58501,"2013":27478,"2011":51167,"2006":8067,"2008":30427,"2009":47014,"2005":12176,"2007":14242,"2002":527,"2004":49,"2003":230}},{"snapshot_date":"2020-12","composition":{"2018":61019,"2019":54317,"2020":114522,"2014":18594,"2017":145129,"2015":28817,"2010":19070,"2016":33480,"2012":58445,"2013":26876,"2011":50577,"2008":30008,"2009":46735,"2006":8047,"2005":12142,"2007":14182,"2002":526,"2004":49,"2003":230}},{"snapshot_date":"2021-03","composition":{"2018":61349,"2020":104729,"2019":54845,"2014":18621,"2017":145434,"2015":29101,"2012":58457,"2016":33526,"2010":19104,"2013":27231,"2011":50614,"2008":30056,"2009":46855,"2006":8051,"2005":12145,"2007":14189,"2021":2222,"2002":527,"2004":49,"2003":230}},{"snapshot_date":"2021-06","composition":{"2020":112028,"2021":45055,"2018":59930,"2019":53018,"2015":28456,"2010":18935,"2016":33218,"2012":58314,"2017":144293,"2014":18363,"2013":26466,"2011":47525,"2006":8013,"2008":30260,"2009":46200,"2005":12104,"2007":14141,"2002":524,"2004":49,"2003":230}},{"snapshot_date":"2021-09","composition":{"2018":59052,"2021":97402,"2019":52650,"2020":109267,"2015":27816,"2010":18656,"2016":33061,"2012":58200,"2017":143713,"2014":17505,"2013":25955,"2011":46683,"2006":7931,"2008":29288,"2009":45809,"2005":11659,"2007":13859,"2002":519,"2004":48,"2003":230}},{"snapshot_date":"2021-12","composition":{"2020":106648,"2021":121834,"2018":58489,"2019":52144,"2015":27360,"2010":17939,"2016":32977,"2012":57388,"2017":143312,"2014":16983,"2013":25758,"2011":46229,"2008":29088,"2006":7907,"2005":11544,"2009":45709,"2007":13832,"2002":519,"2004":48,"2003":230}},{"snapshot_date":"2022-03","composition":{"2021":125502,"2018":58156,"2019":51279,"2020":105366,"2022":17765,"2015":26951,"2012":56311,"2016":32844,"2013":25031,"2010":17808,"2017":143069,"2014":16635,"2011":46017,"2008":28815,"2009":45556,"2005":11537,"2007":13822,"2006":7895,"2002":519,"2004":48,"2003":230}},{"snapshot_date":"2022-06","composition":{"2018":57807,"2021":126196,"2020":104264,"2019":50945,"2022":37665,"2015":26535,"2010":17599,"2016":32719,"2012":55823,"2017":142586,"2014":16534,"2013":24376,"2011":45697,"2008":28642,"2009":44994,"2005":11438,"2006":7797,"2007":13733,"2002":516,"2004":48,"2003":229}},{"snapshot_date":"2022-09","composition":{"2020":103997,"2018":57726,"2021":125728,"2022":42874,"2019":50800,"2015":26468,"2010":17534,"2016":32655,"2012":55811,"2017":142510,"2014":16422,"2013":24318,"2011":45687,"2006":7794,"2008":28526,"2009":44517,"2005":11438,"2007":13668,"2002":516,"2004":48,"2003":229}},{"snapshot_date":"2022-12","composition":{"2022":55955,"2018":57356,"2019":50605,"2021":124787,"2020":103290,"2015":25906,"2010":17490,"2016":32562,"2012":55746,"2017":142114,"2014":16365,"2013":23784,"2011":45494,"2006":7761,"2005":11405,"2009":44247,"2008":28450,"2007":13577,"2002":516,"2004":48,"2003":229}},{"snapshot_date":"2023-03","composition":{"2022":59168,"2023":10358,"2021":120524,"2018":56770,"2020":100976,"2019":49742,"2015":25710,"2008":28012,"2016":32315,"2009":44088,"2014":16282,"2017":140354,"2010":17341,"2012":55648,"2013":23482,"2011":45145,"2006":7683,"2005":11389,"2007":13558,"2002":516,"2004":48,"2003":228}},{"snapshot_date":"2023-06","composition":{"2022":58437,"2023":34207,"2021":107984,"2020":100173,"2018":56373,"2019":49163,"2015":25495,"2010":16797,"2016":32250,"2012":55502,"2017":139945,"2014":16125,"2013":23318,"2011":44806,"2008":27784,"2009":43728,"2006":7209,"2005":11334,"2007":13493,"2002":516,"2004":48,"2003":227}},{"snapshot_date":"2023-09","composition":{"2022":55700,"2023":62491,"2021":100401,"2020":94581,"2018":55435,"2019":47753,"2015":24632,"2016":30433,"2017":138546,"2012":55265,"2010":16400,"2014":15672,"2013":22552,"2011":44120,"2008":26921,"2009":41512,"2006":6928,"2005":11158,"2007":13056,"2004":48,"2003":220,"2002":509}},{"snapshot_date":"2023-12","composition":{"2022":54599,"2023":91904,"2021":98879,"2018":53945,"2020":93516,"2019":47067,"2015":24156,"2010":15736,"2016":30177,"2012":54822,"2017":137828,"2014":15179,"2013":21937,"2011":43821,"2006":6464,"2008":26233,"2009":40323,"2005":10910,"2007":12624,"2004":31,"2003":217,"2002":486}},{"snapshot_date":"2024-03","composition":{"2018":50761,"2019":46690,"2022":52839,"2023":83911,"2021":93662,"2020":92785,"2024":30118,"2015":24044,"2010":15661,"2016":30014,"2012":54601,"2017":137644,"2014":15119,"2013":21678,"2011":43446,"2006":6389,"2008":26095,"2005":10883,"2009":39081,"2007":12600,"2004":31,"2003":217,"2002":485}},{"snapshot_date":"2024-06","composition":{"2023":82879,"2024":39429,"2022":52334,"2021":93118,"2018":50598,"2020":92278,"2019":46371,"2015":23986,"2010":15623,"2016":29981,"2012":54472,"2017":137433,"2014":15091,"2013":21640,"2011":43348,"2005":10869,"2008":26022,"2007":12593,"2009":39036,"2006":6379,"2004":31,"2003":217,"2002":485}},{"snapshot_date":"2024-09","composition":{"2023":81060,"2024":58495,"2022":51245,"2021":91299,"2018":49868,"2015":23851,"2019":45868,"2020":91654,"2010":15329,"2016":29872,"2012":54407,"2017":137201,"2014":14959,"2013":21558,"2011":42777,"2006":6362,"2008":25874,"2009":38030,"2005":10829,"2007":12568,"2004":31,"2003":217,"2002":485}},{"snapshot_date":"2024-12","composition":{"2022":50651,"2023":79580,"2020":90805,"2021":89441,"2024":79718,"2018":49399,"2019":45459,"2015":23478,"2010":15070,"2016":29631,"2012":54260,"2017":136825,"2014":14558,"2013":20957,"2011":40920,"2006":6288,"2008":25636,"2009":37605,"2005":10787,"2007":12206,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-01","composition":{"2021":89243,"2022":50615,"2023":79451,"2018":49378,"2024":80020,"2020":90749,"2015":23478,"2019":45451,"2025":2383,"2010":15065,"2016":29621,"2012":54234,"2017":136711,"2014":14557,"2013":20932,"2011":40903,"2008":25635,"2009":37562,"2006":6288,"2005":10787,"2007":12206,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-02","composition":{"2022":50496,"2023":79170,"2021":89103,"2025":6210,"2024":79734,"2018":49255,"2020":90244,"2019":45295,"2015":23474,"2012":54148,"2016":29571,"2017":136630,"2010":15058,"2014":14553,"2013":20915,"2011":40815,"2006":6278,"2005":10787,"2008":25633,"2007":12204,"2009":37544,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-03","composition":{"2022":49804,"2023":78817,"2021":88041,"2024":78977,"2025":12625,"2018":49216,"2019":45287,"2020":90074,"2015":23424,"2010":15051,"2016":29570,"2012":54148,"2017":136627,"2014":14545,"2013":20864,"2011":40815,"2006":6278,"2008":25623,"2009":37543,"2005":10787,"2007":11959,"2004":23,"2003":211,"2002":469}},{"snapshot_date":"2025-04","composition":{"2018":49092,"2023":77780,"2025":19503,"2024":77274,"2021":86816,"2022":49391,"2020":89794,"2019":45201,"2015":23102,"2010":15012,"2016":29497,"2012":54125,"2017":136524,"2014":14525,"2013":20769,"2011":40682,"2008":25591,"2009":37512,"2005":10762,"2007":11951,"2006":6270,"2004":23,"2003":211,"2002":464}},{"snapshot_date":"2025-05","composition":{"2022":49089,"2023":77083,"2018":48737,"2020":89322,"2021":85760,"2025":27815,"2024":76096,"2019":45009,"2015":22923,"2012":54096,"2016":29405,"2017":136356,"2008":25546,"2009":37291,"2014":14458,"2010":14979,"2013":20628,"2011":40637,"2006":6254,"2005":10703,"2007":11866,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-06","composition":{"2022":49033,"2023":76925,"2021":85569,"2025":32977,"2020":89202,"2024":75818,"2018":48697,"2019":44978,"2015":22919,"2010":14972,"2016":29396,"2012":54070,"2017":136354,"2014":14454,"2013":20607,"2011":40621,"2005":10703,"2008":25546,"2007":11865,"2006":6254,"2009":37289,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-07","composition":{"2022":48964,"2023":76778,"2021":85458,"2025":35930,"2020":89056,"2018":48554,"2019":44888,"2024":75473,"2015":22890,"2010":14950,"2016":29238,"2012":54062,"2017":136300,"2014":14447,"2013":20592,"2011":40589,"2008":25513,"2009":37219,"2006":6254,"2005":10699,"2007":11858,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-08","composition":{"2021":84948,"2025":39378,"2022":48745,"2023":76487,"2020":88961,"2024":75168,"2018":48540,"2019":44846,"2015":22809,"2012":54022,"2016":29174,"2017":136261,"2010":14906,"2014":14425,"2013":20457,"2011":40518,"2008":25462,"2009":36955,"2006":6254,"2005":10699,"2007":11844,"2003":211,"2004":22,"2002":450}},{"snapshot_date":"2025-09","composition":{"2022":48340,"2023":76041,"2020":88796,"2021":81876,"2025":44888,"2024":74576,"2018":48164,"2015":22727,"2019":44345,"2010":14874,"2016":28894,"2012":53973,"2017":136220,"2014":14395,"2013":20405,"2011":40432,"2008":25414,"2009":36912,"2006":6223,"2005":10693,"2007":11841,"2004":22,"2003":211,"2002":450}},{"snapshot_date":"2025-10","composition":{"2022":48192,"2023":75293,"2021":80321,"2025":53113,"2018":47992,"2020":88588,"2024":72865,"2019":44311,"2015":22561,"2010":14854,"2016":28862,"2012":53957,"2017":136093,"2014":14375,"2013":20347,"2011":40375,"2006":6195,"2008":25189,"2005":10677,"2009":36813,"2007":11833,"2004":17,"2002":442,"2003":46}},{"snapshot_date":"2025-11","composition":{"2021":79605,"2025":59992,"2022":47943,"2023":74782,"2024":72354,"2020":88240,"2018":47831,"2015":22537,"2019":44191,"2010":14843,"2016":28752,"2012":53950,"2017":135910,"2014":14359,"2013":20274,"2011":40175,"2008":25102,"2009":36633,"2006":6059,"2005":10673,"2007":11780,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2025-12","composition":{"2021":77624,"2025":66882,"2022":47371,"2023":72787,"2018":47273,"2020":83371,"2024":70724,"2019":42674,"2015":21873,"2012":53465,"2016":28263,"2017":134788,"2010":14549,"2014":13988,"2013":19504,"2011":39948,"2008":24669,"2009":34785,"2006":4411,"2005":6078,"2007":9963,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-01","composition":{"2023":72372,"2025":66534,"2024":70327,"2022":47318,"2021":76902,"2018":47222,"2020":83204,"2019":42563,"2026":5958,"2015":21829,"2010":14537,"2016":28262,"2012":53457,"2017":134734,"2014":13972,"2013":19463,"2011":39886,"2006":4399,"2008":24649,"2009":34693,"2005":6067,"2007":9953,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-02","composition":{"2018":46641,"2022":47278,"2023":72221,"2021":76791,"2025":66399,"2020":82827,"2024":69905,"2015":21821,"2019":42547,"2026":7958,"2012":53453,"2016":28172,"2017":134717,"2008":24622,"2009":34684,"2014":13935,"2010":14529,"2013":19456,"2011":39882,"2006":4398,"2005":6064,"2007":9946,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-03","composition":{"2023":72001,"2025":66092,"2024":69743,"2022":47221,"2021":76469,"2018":46580,"2020":82565,"2019":42499,"2015":21647,"2026":12114,"2010":14494,"2016":28145,"2012":53430,"2017":134676,"2014":13906,"2013":19444,"2011":39765,"2008":24608,"2009":34566,"2006":4397,"2005":6057,"2007":9938,"2004":17,"2002":439,"2003":46}},{"snapshot_date":"2026-04","composition":{"2021":76438,"2025":65899,"2022":47184,"2023":71932,"2024":69683,"2018":46568,"2026":13802,"2020":82535,"2019":42499,"2015":21641,"2012":53424,"2016":28137,"2017":134665,"2006":4396,"2010":14491,"2014":13904,"2013":19432,"2011":39759,"2008":24608,"2005":6057,"2009":34553,"2007":9937,"2004":17,"2002":439,"2003":46}}],"fossils":{"genesis":{"timestamp":1008690310,"file":"scipy_distutils/command/__init__.py","content":"\"\"\"distutils.command","year":"2001","commit":"f1a2d63","view_commit":"f1a2d6376c430f65550efa235209b86c1a0967e3","line":1},"survivor":{"timestamp":1017446578,"file":"numpy/lib/_polynomial_impl.py","content":"def poly(seq_of_zeros):","year":"2002","commit":"0562713","view_commit":"main","line":40}}} \ No newline at end of file diff --git a/data/react_data.json b/data/react_data.json index a61dc34..a6b042e 100644 --- a/data/react_data.json +++ b/data/react_data.json @@ -1 +1 @@ -{"snapshots":[{"snapshot_date":"2013-06","composition":{"2013":44058}},{"snapshot_date":"2013-09","composition":{"2013":56640}},{"snapshot_date":"2013-12","composition":{"2013":103418}},{"snapshot_date":"2014-03","composition":{"2014":14460,"2013":124672}},{"snapshot_date":"2014-06","composition":{"2013":122748,"2014":28040}},{"snapshot_date":"2014-09","composition":{"2013":120753,"2014":43942}},{"snapshot_date":"2014-12","composition":{"2013":115042,"2014":61057}},{"snapshot_date":"2015-03","composition":{"2015":12814,"2013":111731,"2014":68309}},{"snapshot_date":"2015-06","composition":{"2015":99379,"2014":64525,"2013":108918}},{"snapshot_date":"2015-09","composition":{"2015":124048,"2014":61377,"2013":99751}},{"snapshot_date":"2015-12","composition":{"2013":97720,"2015":146057,"2014":66057}},{"snapshot_date":"2016-03","composition":{"2015":152293,"2016":8454,"2014":65616,"2013":97372}},{"snapshot_date":"2016-06","composition":{"2013":92612,"2015":145240,"2014":58111,"2016":56315}},{"snapshot_date":"2016-09","composition":{"2016":97634,"2014":56984,"2015":128249,"2013":91397}},{"snapshot_date":"2016-12","composition":{"2013":87455,"2015":93943,"2014":53615,"2016":165406}},{"snapshot_date":"2017-03","composition":{"2016":166245,"2017":18369,"2014":53388,"2015":93195,"2013":87174}},{"snapshot_date":"2017-06","composition":{"2016":114802,"2017":97714,"2014":49195,"2013":80876,"2015":85734}},{"snapshot_date":"2017-09","composition":{"2016":100286,"2017":137649,"2014":44904,"2013":73677,"2015":81253}},{"snapshot_date":"2017-12","composition":{"2014":3296,"2016":37649,"2017":130122,"2015":9732,"2013":4642}},{"snapshot_date":"2018-03","composition":{"2017":133361,"2016":31594,"2018":25647,"2015":8897,"2013":4240,"2014":2846}},{"snapshot_date":"2018-06","composition":{"2013":4178,"2015":8798,"2014":2813,"2016":28955,"2017":125888,"2018":47644}},{"snapshot_date":"2018-09","composition":{"2017":123086,"2015":8701,"2016":27872,"2018":80461,"2014":2783,"2013":4085}},{"snapshot_date":"2018-12","composition":{"2017":119475,"2016":27687,"2013":4028,"2018":109144,"2015":8663,"2014":2745}},{"snapshot_date":"2019-03","composition":{"2018":105961,"2014":2745,"2017":119065,"2015":8661,"2016":27318,"2013":4026,"2019":17887}},{"snapshot_date":"2019-06","composition":{"2019":47716}},{"snapshot_date":"2019-09","composition":{"2019":144487,"2013":4006,"2015":8643,"2014":2733,"2017":116957,"2018":85535,"2016":26456}},{"snapshot_date":"2019-12","composition":{"2015":8633,"2017":115880,"2016":26323,"2014":2731,"2013":4006,"2019":173378,"2018":83042}},{"snapshot_date":"2020-03","composition":{"2014":2628,"2020":25299,"2019":161223,"2018":77097,"2016":25834,"2017":113668,"2013":3813,"2015":8533}},{"snapshot_date":"2020-06","composition":{"2020":107514,"2017":111700,"2019":151449,"2018":74527,"2013":3705,"2015":8475,"2016":24830,"2014":2635}},{"snapshot_date":"2020-09","composition":{"2019":134359,"2016":24617,"2018":71821,"2020":130649,"2017":109402,"2015":8375,"2014":2117,"2013":3432}},{"snapshot_date":"2020-12","composition":{"2015":8365,"2014":2109,"2016":24570,"2013":3381,"2019":132051,"2020":161173,"2017":109318,"2018":71602}},{"snapshot_date":"2021-03","composition":{"2017":106680,"2019":123247,"2021":14784,"2014":2109,"2018":71013,"2020":174962,"2013":3381,"2015":8365,"2016":24563}},{"snapshot_date":"2021-06","composition":{"2013":3366,"2018":64817,"2020":164228,"2021":51873,"2017":104862,"2015":8362,"2019":117274,"2014":2097,"2016":24443}},{"snapshot_date":"2021-09","composition":{"2019":114891,"2021":100659,"2020":157591,"2015":8299,"2014":2055,"2016":24342,"2013":3366,"2017":104459,"2018":64231}},{"snapshot_date":"2021-12","composition":{"2019":113210,"2021":132852,"2015":8119,"2017":103777,"2016":24244,"2014":2047,"2013":3347,"2020":151036,"2018":63731}},{"snapshot_date":"2022-03","composition":{"2020":149490,"2021":132083,"2019":111676,"2017":103610,"2022":20641,"2018":63553,"2015":8093,"2016":24233,"2013":3347,"2014":2036}},{"snapshot_date":"2022-06","composition":{"2021":128680,"2020":146266,"2014":2036,"2013":3343,"2015":8081,"2019":105978,"2017":103567,"2022":42376,"2018":62371,"2016":24217}},{"snapshot_date":"2022-09","composition":{"2021":125186,"2017":102835,"2019":101050,"2022":65451,"2020":141043,"2018":61418,"2015":8069,"2014":2036,"2016":24214,"2013":3343}},{"snapshot_date":"2022-12","composition":{"2019":97224,"2021":122925,"2020":136881,"2017":101550,"2015":8038,"2016":24156,"2018":59931,"2022":99388,"2013":3308,"2014":2030}},{"snapshot_date":"2023-03","composition":{"2019":95301,"2021":113457,"2022":78827,"2023":25935,"2020":102538,"2017":99498,"2015":7993,"2016":23711,"2013":3258,"2014":2005,"2018":58919}},{"snapshot_date":"2023-06","composition":{"2022":17170,"2021":61003,"2023":45427}},{"snapshot_date":"2023-09","composition":{"2023":278928,"2021":55707,"2022":9406}},{"snapshot_date":"2023-12","composition":{"2023":315798,"2021":58578,"2022":10022}},{"snapshot_date":"2024-03","composition":{"2021":74640}},{"snapshot_date":"2024-06","composition":{"2024":131231,"2023":389106,"2022":70231,"2019":76856,"2020":86835,"2021":110365,"2013":2827,"2015":6468,"2014":1438,"2017":91522,"2018":51463,"2016":21206}},{"snapshot_date":"2024-09","composition":{"2024":186038,"2020":85181,"2017":91329,"2015":6450,"2016":21094,"2019":74478,"2021":107827,"2014":1434,"2023":376610,"2013":2827,"2018":50963,"2022":67418}},{"snapshot_date":"2024-12","composition":{"2024":222258,"2023":371161,"2018":50452,"2021":106543,"2017":91147,"2019":73494,"2022":65464,"2013":2798,"2016":21016,"2015":6434,"2020":84332,"2014":1428}},{"snapshot_date":"2025-01","composition":{"2019":72870,"2024":235398,"2023":369720,"2020":83431,"2021":105680,"2022":64957,"2013":2798,"2018":49935,"2017":91080,"2015":6409,"2016":21010,"2014":1424,"2025":77}},{"snapshot_date":"2025-02","composition":{"2021":105294,"2019":72002,"2020":81905,"2013":2794,"2015":6344,"2014":1412,"2024":228979,"2017":90754,"2022":64653,"2023":146990,"2016":20945,"2018":48938,"2025":30519}},{"snapshot_date":"2025-03","composition":{"2024":226806,"2020":81740,"2025":43402,"2017":90722,"2015":6341,"2016":20945,"2019":70686,"2021":105234,"2023":145909,"2014":1412,"2013":2793,"2022":64519,"2018":48842}},{"snapshot_date":"2025-04","composition":{"2023":144980,"2021":105085,"2024":221556,"2025":76221,"2019":70526,"2020":81491,"2017":90713,"2022":63742,"2014":1412,"2013":2793,"2016":20943,"2015":6341,"2018":48764}},{"snapshot_date":"2025-05","composition":{"2024":220417,"2013":2793,"2016":20943,"2022":63508,"2015":6341,"2023":143478,"2018":48758,"2017":90707,"2019":70454,"2020":81428,"2014":1412,"2021":103829,"2025":95247}},{"snapshot_date":"2025-06","composition":{"2024":218219,"2025":112502,"2023":142802,"2021":103776,"2020":81289,"2013":2793,"2018":48737,"2017":90702,"2015":6341,"2019":70363,"2022":63378,"2016":20942,"2014":1412}},{"snapshot_date":"2025-07","composition":{"2024":215841,"2025":137355,"2019":70222,"2021":103736,"2023":142387,"2020":81261,"2017":90701,"2015":6341,"2016":20939,"2014":1412,"2013":2793,"2018":48694,"2022":63159}},{"snapshot_date":"2025-08","composition":{"2024":212777,"2020":81060,"2025":153793,"2019":69618,"2021":103392,"2023":141860,"2014":1412,"2017":90690,"2015":6334,"2016":20929,"2013":2793,"2018":48331,"2022":62740}},{"snapshot_date":"2025-09","composition":{"2024":209350,"2020":80847,"2025":185175,"2019":69500,"2021":103253,"2023":140410,"2013":2793,"2015":6334,"2014":1412,"2017":90685,"2016":20929,"2018":48318,"2022":62013}},{"snapshot_date":"2025-10","composition":{"2017":90684,"2015":6334,"2016":20929,"2019":69355,"2024":208263,"2021":103074,"2020":80758,"2025":199752,"2023":139982,"2014":1412,"2018":48312,"2022":61753,"2013":2793}},{"snapshot_date":"2025-11","composition":{"2024":207430,"2025":209692,"2019":69272,"2021":103036,"2020":80677,"2023":139651,"2014":1412,"2017":90684,"2015":6334,"2016":20929,"2018":48310,"2022":61693,"2013":2793}},{"snapshot_date":"2025-12","composition":{"2020":80599,"2024":205774,"2025":220530,"2021":103015,"2023":136868,"2019":69017,"2014":1412,"2013":2793,"2015":6334,"2017":90652,"2016":20915,"2018":48277,"2022":61679}},{"snapshot_date":"2026-01","composition":{"2026":19721,"2024":203679,"2020":80546,"2025":223578,"2014":1412,"2019":68844,"2021":102929,"2013":2793,"2015":4433,"2023":136219,"2016":20907,"2018":48198,"2022":61557,"2017":90634}},{"snapshot_date":"2026-02","composition":{"2026":33082,"2024":195521,"2025":215585,"2019":68808,"2021":102919,"2020":80409,"2013":2792,"2015":4433,"2014":1412,"2023":134343,"2017":90634,"2016":20907,"2018":48198,"2022":61380}},{"snapshot_date":"2026-03","composition":{"2026":38141,"2024":195277,"2025":215136,"2021":102784,"2019":68486,"2020":80280,"2017":90630,"2015":4433,"2016":20907,"2018":48092,"2022":61321,"2023":134227,"2013":2792,"2014":1412}},{"snapshot_date":"2026-04","composition":{"2026":42267,"2024":195277,"2025":215136,"2019":68486,"2021":102784,"2020":80280,"2016":20907,"2018":48092,"2022":61321,"2017":90630,"2023":134227,"2014":1412,"2015":4433,"2013":2792}}],"fossils":{"genesis":{"timestamp":1369760685,"file":"vendor/jasmine/jasmine-support.js","content":"return _it.call(this, desc, func);","year":"2013","commit":"c740373","line":40},"survivor":{"timestamp":1369760685,"file":"vendor/jasmine/jasmine-support.js","content":"return _it.call(this, desc, func);","year":"2013","commit":"c740373","line":40}}} \ No newline at end of file +{"snapshots":[{"snapshot_date":"2013-06","composition":{"2013":44058}},{"snapshot_date":"2013-09","composition":{"2013":56640}},{"snapshot_date":"2013-12","composition":{"2013":103418}},{"snapshot_date":"2014-03","composition":{"2014":14460,"2013":124672}},{"snapshot_date":"2014-06","composition":{"2013":122748,"2014":28040}},{"snapshot_date":"2014-09","composition":{"2013":120753,"2014":43942}},{"snapshot_date":"2014-12","composition":{"2013":115042,"2014":61057}},{"snapshot_date":"2015-03","composition":{"2015":12814,"2013":111731,"2014":68309}},{"snapshot_date":"2015-06","composition":{"2015":99379,"2014":64525,"2013":108918}},{"snapshot_date":"2015-09","composition":{"2015":124048,"2014":61377,"2013":99751}},{"snapshot_date":"2015-12","composition":{"2013":97720,"2015":146057,"2014":66057}},{"snapshot_date":"2016-03","composition":{"2015":152293,"2016":8454,"2014":65616,"2013":97372}},{"snapshot_date":"2016-06","composition":{"2013":92612,"2015":145240,"2014":58111,"2016":56315}},{"snapshot_date":"2016-09","composition":{"2016":97634,"2014":56984,"2015":128249,"2013":91397}},{"snapshot_date":"2016-12","composition":{"2013":87455,"2015":93943,"2014":53615,"2016":165406}},{"snapshot_date":"2017-03","composition":{"2016":166245,"2017":18369,"2014":53388,"2015":93195,"2013":87174}},{"snapshot_date":"2017-06","composition":{"2016":114802,"2017":97714,"2014":49195,"2013":80876,"2015":85734}},{"snapshot_date":"2017-09","composition":{"2016":100286,"2017":137649,"2014":44904,"2013":73677,"2015":81253}},{"snapshot_date":"2017-12","composition":{"2014":3296,"2016":37649,"2017":130122,"2015":9732,"2013":4642}},{"snapshot_date":"2018-03","composition":{"2017":133361,"2016":31594,"2018":25647,"2015":8897,"2013":4240,"2014":2846}},{"snapshot_date":"2018-06","composition":{"2013":4178,"2015":8798,"2014":2813,"2016":28955,"2017":125888,"2018":47644}},{"snapshot_date":"2018-09","composition":{"2017":123086,"2015":8701,"2016":27872,"2018":80461,"2014":2783,"2013":4085}},{"snapshot_date":"2018-12","composition":{"2017":119475,"2016":27687,"2013":4028,"2018":109144,"2015":8663,"2014":2745}},{"snapshot_date":"2019-03","composition":{"2018":105961,"2014":2745,"2017":119065,"2015":8661,"2016":27318,"2013":4026,"2019":17887}},{"snapshot_date":"2019-06","composition":{"2019":47716}},{"snapshot_date":"2019-09","composition":{"2019":144487,"2013":4006,"2015":8643,"2014":2733,"2017":116957,"2018":85535,"2016":26456}},{"snapshot_date":"2019-12","composition":{"2015":8633,"2017":115880,"2016":26323,"2014":2731,"2013":4006,"2019":173378,"2018":83042}},{"snapshot_date":"2020-03","composition":{"2014":2628,"2020":25299,"2019":161223,"2018":77097,"2016":25834,"2017":113668,"2013":3813,"2015":8533}},{"snapshot_date":"2020-06","composition":{"2020":107514,"2017":111700,"2019":151449,"2018":74527,"2013":3705,"2015":8475,"2016":24830,"2014":2635}},{"snapshot_date":"2020-09","composition":{"2019":134359,"2016":24617,"2018":71821,"2020":130649,"2017":109402,"2015":8375,"2014":2117,"2013":3432}},{"snapshot_date":"2020-12","composition":{"2015":8365,"2014":2109,"2016":24570,"2013":3381,"2019":132051,"2020":161173,"2017":109318,"2018":71602}},{"snapshot_date":"2021-03","composition":{"2017":106680,"2019":123247,"2021":14784,"2014":2109,"2018":71013,"2020":174962,"2013":3381,"2015":8365,"2016":24563}},{"snapshot_date":"2021-06","composition":{"2013":3366,"2018":64817,"2020":164228,"2021":51873,"2017":104862,"2015":8362,"2019":117274,"2014":2097,"2016":24443}},{"snapshot_date":"2021-09","composition":{"2019":114891,"2021":100659,"2020":157591,"2015":8299,"2014":2055,"2016":24342,"2013":3366,"2017":104459,"2018":64231}},{"snapshot_date":"2021-12","composition":{"2019":113210,"2021":132852,"2015":8119,"2017":103777,"2016":24244,"2014":2047,"2013":3347,"2020":151036,"2018":63731}},{"snapshot_date":"2022-03","composition":{"2020":149490,"2021":132083,"2019":111676,"2017":103610,"2022":20641,"2018":63553,"2015":8093,"2016":24233,"2013":3347,"2014":2036}},{"snapshot_date":"2022-06","composition":{"2021":128680,"2020":146266,"2014":2036,"2013":3343,"2015":8081,"2019":105978,"2017":103567,"2022":42376,"2018":62371,"2016":24217}},{"snapshot_date":"2022-09","composition":{"2021":125186,"2017":102835,"2019":101050,"2022":65451,"2020":141043,"2018":61418,"2015":8069,"2014":2036,"2016":24214,"2013":3343}},{"snapshot_date":"2022-12","composition":{"2019":97224,"2021":122925,"2020":136881,"2017":101550,"2015":8038,"2016":24156,"2018":59931,"2022":99388,"2013":3308,"2014":2030}},{"snapshot_date":"2023-03","composition":{"2019":95301,"2021":113457,"2022":78827,"2023":25935,"2020":102538,"2017":99498,"2015":7993,"2016":23711,"2013":3258,"2014":2005,"2018":58919}},{"snapshot_date":"2023-06","composition":{"2022":17170,"2021":61003,"2023":45427}},{"snapshot_date":"2023-09","composition":{"2023":278928,"2021":55707,"2022":9406}},{"snapshot_date":"2023-12","composition":{"2023":315798,"2021":58578,"2022":10022}},{"snapshot_date":"2024-03","composition":{"2021":74640}},{"snapshot_date":"2024-06","composition":{"2024":131231,"2023":389106,"2022":70231,"2019":76856,"2020":86835,"2021":110365,"2013":2827,"2015":6468,"2014":1438,"2017":91522,"2018":51463,"2016":21206}},{"snapshot_date":"2024-09","composition":{"2024":186038,"2020":85181,"2017":91329,"2015":6450,"2016":21094,"2019":74478,"2021":107827,"2014":1434,"2023":376610,"2013":2827,"2018":50963,"2022":67418}},{"snapshot_date":"2024-12","composition":{"2024":222258,"2023":371161,"2018":50452,"2021":106543,"2017":91147,"2019":73494,"2022":65464,"2013":2798,"2016":21016,"2015":6434,"2020":84332,"2014":1428}},{"snapshot_date":"2025-01","composition":{"2019":72870,"2024":235398,"2023":369720,"2020":83431,"2021":105680,"2022":64957,"2013":2798,"2018":49935,"2017":91080,"2015":6409,"2016":21010,"2014":1424,"2025":77}},{"snapshot_date":"2025-02","composition":{"2021":105294,"2019":72002,"2020":81905,"2013":2794,"2015":6344,"2014":1412,"2024":228979,"2017":90754,"2022":64653,"2023":146990,"2016":20945,"2018":48938,"2025":30519}},{"snapshot_date":"2025-03","composition":{"2024":226806,"2020":81740,"2025":43402,"2017":90722,"2015":6341,"2016":20945,"2019":70686,"2021":105234,"2023":145909,"2014":1412,"2013":2793,"2022":64519,"2018":48842}},{"snapshot_date":"2025-04","composition":{"2023":144980,"2021":105085,"2024":221556,"2025":76221,"2019":70526,"2020":81491,"2017":90713,"2022":63742,"2014":1412,"2013":2793,"2016":20943,"2015":6341,"2018":48764}},{"snapshot_date":"2025-05","composition":{"2024":220417,"2013":2793,"2016":20943,"2022":63508,"2015":6341,"2023":143478,"2018":48758,"2017":90707,"2019":70454,"2020":81428,"2014":1412,"2021":103829,"2025":95247}},{"snapshot_date":"2025-06","composition":{"2024":218219,"2025":112502,"2023":142802,"2021":103776,"2020":81289,"2013":2793,"2018":48737,"2017":90702,"2015":6341,"2019":70363,"2022":63378,"2016":20942,"2014":1412}},{"snapshot_date":"2025-07","composition":{"2024":215841,"2025":137355,"2019":70222,"2021":103736,"2023":142387,"2020":81261,"2017":90701,"2015":6341,"2016":20939,"2014":1412,"2013":2793,"2018":48694,"2022":63159}},{"snapshot_date":"2025-08","composition":{"2024":212777,"2020":81060,"2025":153793,"2019":69618,"2021":103392,"2023":141860,"2014":1412,"2017":90690,"2015":6334,"2016":20929,"2013":2793,"2018":48331,"2022":62740}},{"snapshot_date":"2025-09","composition":{"2024":209350,"2020":80847,"2025":185175,"2019":69500,"2021":103253,"2023":140410,"2013":2793,"2015":6334,"2014":1412,"2017":90685,"2016":20929,"2018":48318,"2022":62013}},{"snapshot_date":"2025-10","composition":{"2017":90684,"2015":6334,"2016":20929,"2019":69355,"2024":208263,"2021":103074,"2020":80758,"2025":199752,"2023":139982,"2014":1412,"2018":48312,"2022":61753,"2013":2793}},{"snapshot_date":"2025-11","composition":{"2024":207430,"2025":209692,"2019":69272,"2021":103036,"2020":80677,"2023":139651,"2014":1412,"2017":90684,"2015":6334,"2016":20929,"2018":48310,"2022":61693,"2013":2793}},{"snapshot_date":"2025-12","composition":{"2020":80599,"2024":205774,"2025":220530,"2021":103015,"2023":136868,"2019":69017,"2014":1412,"2013":2793,"2015":6334,"2017":90652,"2016":20915,"2018":48277,"2022":61679}},{"snapshot_date":"2026-01","composition":{"2026":19721,"2024":203679,"2020":80546,"2025":223578,"2014":1412,"2019":68844,"2021":102929,"2013":2793,"2015":4433,"2023":136219,"2016":20907,"2018":48198,"2022":61557,"2017":90634}},{"snapshot_date":"2026-02","composition":{"2026":33082,"2024":195521,"2025":215585,"2019":68808,"2021":102919,"2020":80409,"2013":2792,"2015":4433,"2014":1412,"2023":134343,"2017":90634,"2016":20907,"2018":48198,"2022":61380}},{"snapshot_date":"2026-03","composition":{"2026":38141,"2024":195277,"2025":215136,"2021":102784,"2019":68486,"2020":80280,"2017":90630,"2015":4433,"2016":20907,"2018":48092,"2022":61321,"2023":134227,"2013":2792,"2014":1412}},{"snapshot_date":"2026-04","composition":{"2026":42267,"2024":195277,"2025":215136,"2019":68486,"2021":102784,"2020":80280,"2016":20907,"2018":48092,"2022":61321,"2017":90630,"2023":134227,"2014":1412,"2015":4433,"2013":2792}}],"fossils":{"genesis":{"timestamp":1369760685,"file":"vendor/jasmine/jasmine-support.js","content":"return _it.call(this, desc, func);","year":"2013","commit":"c740373","view_commit":"c740373b311a2aa43a512f1bf53e1de72635c02a","line":40},"survivor":{"timestamp":1369856771,"file":".editorconfig","content":"root = true","year":"2013","commit":"75897c2","view_commit":"main","line":2}}} \ No newline at end of file diff --git a/data/zed_data.json b/data/zed_data.json index 42b6c79..5ca4977 100644 --- a/data/zed_data.json +++ b/data/zed_data.json @@ -1 +1 @@ -{"snapshots":[{"snapshot_date":"2021-03","composition":{"2021":25386}},{"snapshot_date":"2021-06","composition":{"2021":44895}},{"snapshot_date":"2021-09","composition":{"2021":76965}},{"snapshot_date":"2021-12","composition":{"2021":93984}},{"snapshot_date":"2022-03","composition":{"2021":73677,"2022":387601}},{"snapshot_date":"2022-06","composition":{"2021":51110,"2022":438719}},{"snapshot_date":"2022-09","composition":{"2021":47921,"2022":461332}},{"snapshot_date":"2022-12","composition":{"2022":491040,"2021":42941}},{"snapshot_date":"2023-03","composition":{"2022":475618,"2023":50000,"2021":39796}},{"snapshot_date":"2023-06","composition":{"2023":113724,"2022":453195,"2021":36084}},{"snapshot_date":"2023-09","composition":{"2022":444145,"2023":201662,"2021":35314}},{"snapshot_date":"2023-12","composition":{"2023":568521,"2022":439153,"2021":34913}},{"snapshot_date":"2024-03","composition":{"2024":190923,"2023":147169,"2022":410686,"2021":22875}},{"snapshot_date":"2024-06","composition":{"2024":309630,"2023":130635,"2022":66647,"2021":21876}},{"snapshot_date":"2024-09","composition":{"2024":441349,"2023":116356,"2021":20941,"2022":61998}},{"snapshot_date":"2024-12","composition":{"2024":522156,"2023":111931,"2022":58843,"2021":19618}},{"snapshot_date":"2025-01","composition":{"2025":87396,"2024":472990,"2023":106552,"2022":56478,"2021":19030}},{"snapshot_date":"2025-02","composition":{"2024":445662,"2025":136662,"2023":104622,"2022":55697,"2021":18947}},{"snapshot_date":"2025-03","composition":{"2025":232121,"2024":420096,"2023":100247,"2022":53002,"2021":18700}},{"snapshot_date":"2025-04","composition":{"2025":295774,"2024":405671,"2023":98891,"2021":18379,"2022":51571}},{"snapshot_date":"2025-05","composition":{"2025":383630,"2024":388978,"2023":97949,"2022":51077,"2021":18342}},{"snapshot_date":"2025-06","composition":{"2025":435520,"2024":381859,"2023":96906,"2022":50493,"2021":18310}},{"snapshot_date":"2025-07","composition":{"2024":370918,"2025":505687,"2023":96078,"2022":50134,"2021":18028}},{"snapshot_date":"2025-08","composition":{"2025":587332,"2024":342164,"2023":94185,"2022":49275,"2021":17791}},{"snapshot_date":"2025-09","composition":{"2025":647281,"2024":322881,"2023":89353,"2022":48437,"2021":17312}},{"snapshot_date":"2025-10","composition":{"2025":785450,"2024":313650,"2023":87163,"2022":47531,"2021":16776}},{"snapshot_date":"2025-11","composition":{"2025":829130,"2024":308220,"2023":85858,"2022":46995,"2021":16515}},{"snapshot_date":"2025-12","composition":{"2025":878519,"2024":303362,"2023":84983,"2022":46559,"2021":16345}},{"snapshot_date":"2026-01","composition":{"2025":827244,"2026":155517,"2024":296030,"2023":82991,"2022":46183,"2021":16051}},{"snapshot_date":"2026-02","composition":{"2026":287230,"2024":286996,"2023":81761,"2025":792483,"2022":44769,"2021":15713}},{"snapshot_date":"2026-03","composition":{"2026":424322,"2025":759181,"2023":80738,"2024":271972,"2022":44357,"2021":15498}},{"snapshot_date":"2026-04","composition":{"2026":449429,"2024":270386,"2025":753702,"2023":80586,"2022":43769,"2021":15325}}],"fossils":{"genesis":{"timestamp":1613840554,"file":".gitignore","content":"/target","year":"2021","commit":"b400449","line":1},"survivor":{"timestamp":1613840554,"file":".gitignore","content":"/target","year":"2021","commit":"b400449","line":1}}} \ No newline at end of file +{"snapshots":[{"snapshot_date":"2021-03","composition":{"2021":25386}},{"snapshot_date":"2021-06","composition":{"2021":44895}},{"snapshot_date":"2021-09","composition":{"2021":76965}},{"snapshot_date":"2021-12","composition":{"2021":93984}},{"snapshot_date":"2022-03","composition":{"2021":73677,"2022":387601}},{"snapshot_date":"2022-06","composition":{"2021":51110,"2022":438719}},{"snapshot_date":"2022-09","composition":{"2021":47921,"2022":461332}},{"snapshot_date":"2022-12","composition":{"2022":491040,"2021":42941}},{"snapshot_date":"2023-03","composition":{"2022":475618,"2023":50000,"2021":39796}},{"snapshot_date":"2023-06","composition":{"2023":113724,"2022":453195,"2021":36084}},{"snapshot_date":"2023-09","composition":{"2022":444145,"2023":201662,"2021":35314}},{"snapshot_date":"2023-12","composition":{"2023":568521,"2022":439153,"2021":34913}},{"snapshot_date":"2024-03","composition":{"2024":190923,"2023":147169,"2022":410686,"2021":22875}},{"snapshot_date":"2024-06","composition":{"2024":309630,"2023":130635,"2022":66647,"2021":21876}},{"snapshot_date":"2024-09","composition":{"2024":441349,"2023":116356,"2021":20941,"2022":61998}},{"snapshot_date":"2024-12","composition":{"2024":522156,"2023":111931,"2022":58843,"2021":19618}},{"snapshot_date":"2025-01","composition":{"2025":87396,"2024":472990,"2023":106552,"2022":56478,"2021":19030}},{"snapshot_date":"2025-02","composition":{"2024":445662,"2025":136662,"2023":104622,"2022":55697,"2021":18947}},{"snapshot_date":"2025-03","composition":{"2025":232121,"2024":420096,"2023":100247,"2022":53002,"2021":18700}},{"snapshot_date":"2025-04","composition":{"2025":295774,"2024":405671,"2023":98891,"2021":18379,"2022":51571}},{"snapshot_date":"2025-05","composition":{"2025":383630,"2024":388978,"2023":97949,"2022":51077,"2021":18342}},{"snapshot_date":"2025-06","composition":{"2025":435520,"2024":381859,"2023":96906,"2022":50493,"2021":18310}},{"snapshot_date":"2025-07","composition":{"2024":370918,"2025":505687,"2023":96078,"2022":50134,"2021":18028}},{"snapshot_date":"2025-08","composition":{"2025":587332,"2024":342164,"2023":94185,"2022":49275,"2021":17791}},{"snapshot_date":"2025-09","composition":{"2025":647281,"2024":322881,"2023":89353,"2022":48437,"2021":17312}},{"snapshot_date":"2025-10","composition":{"2025":785450,"2024":313650,"2023":87163,"2022":47531,"2021":16776}},{"snapshot_date":"2025-11","composition":{"2025":829130,"2024":308220,"2023":85858,"2022":46995,"2021":16515}},{"snapshot_date":"2025-12","composition":{"2025":878519,"2024":303362,"2023":84983,"2022":46559,"2021":16345}},{"snapshot_date":"2026-01","composition":{"2025":827244,"2026":155517,"2024":296030,"2023":82991,"2022":46183,"2021":16051}},{"snapshot_date":"2026-02","composition":{"2026":287230,"2024":286996,"2023":81761,"2025":792483,"2022":44769,"2021":15713}},{"snapshot_date":"2026-03","composition":{"2026":424322,"2025":759181,"2023":80738,"2024":271972,"2022":44357,"2021":15498}},{"snapshot_date":"2026-04","composition":{"2026":449429,"2024":270386,"2025":753702,"2023":80586,"2022":43769,"2021":15325}}],"fossils":{"genesis":{"timestamp":1613840554,"file":"Cargo.toml","content":"[workspace]","year":"2021","commit":"b400449","view_commit":"b400449a58507cca1fa007197929c2cfd6beabbe","line":1},"survivor":{"timestamp":1613840554,"file":"Cargo.toml","content":"[workspace]","year":"2021","commit":"b400449","view_commit":"main","line":1}}} \ No newline at end of file diff --git a/index.html b/index.html index 10835c1..c3e8c99 100644 --- a/index.html +++ b/index.html @@ -64,9 +64,40 @@

The Ship of Theseus

-
- [ Scanning repository history... ] + + +
@@ -192,7 +223,7 @@

Ancient Code Fragments

- 🦕 Historical Fossil + Historical Fossil ----
@@ -202,11 +233,12 @@

Ancient Code Fragments

Loading...
-

The oldest line ever written in this repo's history — even if deleted long ago.

+

The + oldest line ever written in this repo's history, even if deleted long ago.

- 🌿 Living Fossil + Living Fossil ----
@@ -216,7 +248,8 @@

Ancient Code Fragments

Loading...
-

The oldest line that is still alive in the codebase today.

+

The + oldest line that is still alive in the codebase today.

@@ -260,7 +293,8 @@

Where did this all come from?

If you find this digital paradox as fascinating as I do, consider dropping a ⭐ on - GitHub. + GitHub. It helps keep the ship afloat!

@@ -283,4 +317,4 @@

Where did this all come from?

- \ No newline at end of file + diff --git a/scripts/add_fossils.py b/scripts/add_fossils.py index fe2e9e7..98209fd 100644 --- a/scripts/add_fossils.py +++ b/scripts/add_fossils.py @@ -1,7 +1,7 @@ """ -Fossil Finder — Backfill Script -================================ -Adds two fossil types to each repo's data JSON without touching snapshot data: +Fossil Finder — Backfill & Incremental Update Script +===================================================== +Manages two fossil types for each repo's data JSON without touching snapshot data: Genesis (Historical Fossil) — the oldest line **ever written** in this repo's entire git history, found by blaming the very first commit(s). @@ -9,17 +9,24 @@ Survivor (Living Fossil) — the oldest line that is **still alive today**, found by blaming all files at the current default-branch HEAD. -This script is intentionally decoupled from analyse_repository.py so that -fossil data can be refreshed without reprocessing all snapshots. +Modes +----- + (no flags) Full backfill: recompute both Genesis and Survivor for all repos. + --update-survivor Incremental: only refresh the Survivor fossil for each repo, + and only write to disk if the file:line has actually changed. + This is the mode used by the GitHub Actions workflow. + --only REPO Limit processing to a single named repo. """ +import argparse +import concurrent.futures import json -import os import logging +import os import subprocess -import concurrent.futures -from pathlib import Path +import sys from datetime import datetime, timezone +from pathlib import Path logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") logger = logging.getLogger(__name__) @@ -29,6 +36,7 @@ # Helpers # --------------------------------------------------------------------------- + def _run_command(cmd, cwd=None): try: result = subprocess.run( @@ -42,14 +50,24 @@ def _run_command(cmd, cwd=None): ) return result.stdout.strip() except subprocess.CalledProcessError as e: - raise RuntimeError(f"Command failed: {' '.join(str(c) for c in cmd)} — {e.stderr}") from e + raise RuntimeError( + f"Command failed: {' '.join(str(c) for c in cmd)} — {e.stderr}" + ) from e def _blank_fossil(): - return {"timestamp": 2_147_483_647, "file": "", "content": "", "year": "", "commit": "", "line": 0} + return { + "timestamp": 2_147_483_647, + "file": "", + "content": "", + "year": "", + "commit": "", + "view_commit": "", + "line": 0, + } -def _blame_file(repo_path, file_path): +def _blame_file(repo_path, file_path, view_commit=""): """Run git blame --line-porcelain on a single file and return the oldest fossil found.""" try: blame_output = _run_command( @@ -72,8 +90,13 @@ def _blame_file(repo_path, file_path): fossil["timestamp"] = timestamp fossil["file"] = file_path fossil["content"] = content - fossil["year"] = datetime.fromtimestamp(timestamp, timezone.utc).strftime("%Y") + fossil["year"] = datetime.fromtimestamp( + timestamp, timezone.utc + ).strftime("%Y") fossil["commit"] = current_commit_data.get("commit", "")[:7] + fossil["view_commit"] = ( + view_commit # the checkout commit — file is guaranteed to exist here + ) fossil["line"] = line_num else: parts = line.split(" ") @@ -88,14 +111,13 @@ def _blame_file(repo_path, file_path): return fossil -def _blame_files_parallel(repo_path, files, max_workers=20): +def _blame_files_parallel(repo_path, files, view_commit="", max_workers=20): """Blame a list of files in parallel and return the single oldest fossil found.""" global_oldest = _blank_fossil() with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: futures = { - executor.submit(_blame_file, repo_path, f): f - for f in files + executor.submit(_blame_file, repo_path, f, view_commit): f for f in files } for future in concurrent.futures.as_completed(futures): result = future.result() @@ -109,7 +131,8 @@ def _get_tracked_files(repo_path): """Return a list of files that are tracked by git and exist on disk.""" files_output = _run_command(["git", "ls-files"], cwd=repo_path) return [ - f for f in files_output.splitlines() + f + for f in files_output.splitlines() if os.path.isfile(os.path.join(str(repo_path), f)) ] @@ -123,8 +146,8 @@ def _get_default_branch(repo_path): ]: try: result = _run_command(strategy, cwd=repo_path) - # Strip the "origin/" prefix if present - branch = result.split("/")[-1] + # Strip the "origin/" prefix if present without collapsing slashes + branch = result[len("origin/") :] if result.startswith("origin/") else result if branch: return branch except RuntimeError: @@ -133,7 +156,9 @@ def _get_default_branch(repo_path): # Fall back to checking which of the usual suspects exists for branch in ("main", "master", "develop"): try: - _run_command(["git", "rev-parse", "--verify", f"origin/{branch}"], cwd=repo_path) + _run_command( + ["git", "rev-parse", "--verify", f"origin/{branch}"], cwd=repo_path + ) return branch except RuntimeError: continue @@ -141,60 +166,78 @@ def _get_default_branch(repo_path): return "HEAD" +def _fossil_identity(fossil: dict) -> tuple: + """Return a hashable key that identifies which line this fossil refers to. + We use file + line-number + blame commit (the actual authoring commit). + This detects when the living fossil moves to a different line or file. + """ + return (fossil.get("file", ""), fossil.get("line", 0), fossil.get("commit", "")) + + # --------------------------------------------------------------------------- # Genesis — Historical Fossil # --------------------------------------------------------------------------- + def get_genesis_fossil(repo_path, genesis_depth=50): """ Historical Fossil: the oldest line **ever authored** in this repo. - Strategy: git history ordered oldest-first; blame the very first commit. - The root commit is where the repo began — every line in it was authored - at (or before) that moment. We look at genesis_depth commits max as a - safety net but in practice the first commit is definitive. + Strategy: Sort ALL commits by author-time (not committer-time), take the + oldest genesis_depth ones, and blame them. This correctly handles repos + migrated from SVN/Mercurial where old authored lines may appear in commits + with much later committer timestamps. """ logger.info("Computing Genesis (Historical) fossil...") - # Get the oldest commits first (--reverse = oldest to newest) + # Get every commit with its author-time so we can sort by actual authorship date log_output = _run_command( - ["git", "log", "--all", "--reverse", "--pretty=format:%H", f"-n{genesis_depth}"], + ["git", "log", "--all", "--pretty=format:%H %at"], cwd=repo_path, ) - commits = [c for c in log_output.splitlines() if c.strip()] - if not commits: + commit_pairs = [] + for line in log_output.splitlines(): + parts = line.strip().split(" ", 1) + if len(parts) == 2: + try: + commit_pairs.append((parts[0], int(parts[1]))) + except ValueError: + pass + + if not commit_pairs: logger.warning("No commits found in repo.") return _blank_fossil() + # Sort by author-time ascending → oldest authored commits first + commit_pairs.sort(key=lambda x: x[1]) + oldest_commits = [(c[0], c[1]) for c in commit_pairs[:genesis_depth]] + global_oldest = _blank_fossil() - # Optimisation: try just the very first commit first (the root). - # The root commit authored everything that was there from day one. - # Only fall through to deeper scanning if we somehow find nothing. - for i, commit in enumerate(commits): - logger.info(f" Genesis scan: commit {i+1}/{len(commits)} ({commit[:7]})") + for i, (commit, author_ts) in enumerate(oldest_commits): + logger.info( + " Genesis scan: commit %d/%d (%s, at=%s)", + i + 1, + len(oldest_commits), + commit[:7], + author_ts, + ) try: _run_command(["git", "checkout", "--force", commit], cwd=repo_path) except RuntimeError as e: - logger.warning(f" Could not checkout {commit[:7]}: {e}") + logger.warning(" Could not checkout %s: %s", commit[:7], e) continue files = _get_tracked_files(repo_path) if not files: continue - fossil = _blame_files_parallel(repo_path, files) + fossil = _blame_files_parallel(repo_path, files, view_commit=commit) if fossil["file"] and fossil["timestamp"] < global_oldest["timestamp"]: global_oldest = fossil - # Early exit: if we found something in the first commit, - # that IS the oldest possible authored moment — stop here. - if i == 0 and global_oldest["file"]: - logger.info(f" Genesis found in root commit — stopping early.") - break - return global_oldest @@ -202,6 +245,7 @@ def get_genesis_fossil(repo_path, genesis_depth=50): # Survivor — Living Fossil # --------------------------------------------------------------------------- + def get_survivor_fossil(repo_path): """ Living Fossil: the oldest line that is **still alive** in the codebase today. @@ -211,34 +255,46 @@ def get_survivor_fossil(repo_path): logger.info("Computing Survivor (Living) fossil...") default_branch = _get_default_branch(repo_path) - logger.info(f" Checking out default branch: {default_branch}") + logger.info(" Checking out default branch: %s", default_branch) try: - _run_command(["git", "checkout", "--force", default_branch], cwd=repo_path) + _run_command( + ["git", "checkout", "-B", default_branch, f"origin/{default_branch}"], + cwd=repo_path, + ) except RuntimeError: # Detached HEAD fallback - _run_command(["git", "checkout", "--force", f"origin/{default_branch}"], cwd=repo_path) + _run_command( + ["git", "checkout", "--force", f"origin/{default_branch}"], cwd=repo_path + ) + + # For the Living Fossil, link to the branch name directly (not a frozen commit hash). + # This means the GitHub URL points to the current, living file — which is what "living" means. + # The file is guaranteed to exist on this branch since we ls-files it below. + view_commit = default_branch files = _get_tracked_files(repo_path) if not files: logger.warning("No tracked files found at HEAD.") return _blank_fossil() - return _blame_files_parallel(repo_path, files) + return _blame_files_parallel(repo_path, files, view_commit=view_commit) # --------------------------------------------------------------------------- -# Backfill driver +# Full backfill driver # --------------------------------------------------------------------------- + def backfill_fossils(data_dir, repo_urls): """ - For every repo JSON in data_dir, recompute fossils without touching snapshots. + For every repo JSON in data_dir, recompute both fossils without touching snapshots. Always forces a fresh recompute of both genesis and survivor. """ data_path = Path(data_dir) temp_dir = Path("./temp_fossil_repos") temp_dir.mkdir(exist_ok=True) + had_failures = False for json_file in sorted(data_path.glob("*.json")): if json_file.name == "manifest.json": @@ -248,10 +304,10 @@ def backfill_fossils(data_dir, repo_urls): repo_url = repo_urls.get(repo_name) if not repo_url: - logger.warning(f"No URL found for '{repo_name}', skipping.") + logger.warning("No URL found for '%s', skipping.", repo_name) continue - logger.info(f"━━━ Processing: {repo_name} ━━━") + logger.info("━━━ Processing: %s ━━━", repo_name) # 1. Load existing data (snapshots untouched) with open(json_file, "r", encoding="utf-8") as f: @@ -263,20 +319,20 @@ def backfill_fossils(data_dir, repo_urls): snapshots = raw_data.get("snapshots", []) if not snapshots: - logger.warning(f" No snapshots found in {json_file.name}, skipping.") + logger.warning(" No snapshots found in %s, skipping.", json_file.name) continue # 2. Clone the repo if we don't have it locally already local_repo = temp_dir / repo_name if not local_repo.exists(): - logger.info(f" Cloning {repo_url}...") + logger.info(" Cloning %s...", repo_url) _run_command(["git", "clone", repo_url, str(local_repo)]) else: - logger.info(f" Repo already cloned — fetching latest...") + logger.info(" Repo already cloned — fetching latest...") try: _run_command(["git", "fetch", "--all"], cwd=local_repo) except RuntimeError as e: - logger.warning(f" Fetch failed (continuing with local): {e}") + logger.warning(" Fetch failed (continuing with local): %s", e) # 3. Compute fossils try: @@ -287,43 +343,243 @@ def backfill_fossils(data_dir, repo_urls): # Validate — warn if something looks wrong if not genesis.get("file"): - logger.warning(f" ⚠ Genesis fossil is empty for {repo_name}") + logger.warning(" ⚠ Genesis fossil is empty for %s", repo_name) if not survivor.get("file"): - logger.warning(f" ⚠ Survivor fossil is empty for {repo_name}") + logger.warning(" ⚠ Survivor fossil is empty for %s", repo_name) if genesis.get("commit") == survivor.get("commit") and genesis.get("file"): logger.warning( - f" ⚠ Genesis and Survivor share the same commit ({genesis['commit']}) " - f"— this may indicate the repo was never fully rewritten, which is valid, " - f"or there may be a data issue." + "⚠ Genesis and Survivor share the same commit (%s) " + "this may indicate the repo was never fully rewritten, which is valid, " + "or there may be a data issue.", + genesis["commit"], ) logger.info( - f" Genesis → {genesis.get('year')} | {genesis.get('file')}:{genesis.get('line')} | {genesis.get('commit')}" + " Genesis → %s | %s:%s | %s", + genesis.get("year"), + genesis.get("file"), + genesis.get("line"), + genesis.get("commit"), ) logger.info( - f" Survivor → {survivor.get('year')} | {survivor.get('file')}:{survivor.get('line')} | {survivor.get('commit')}" + " Survivor → %s | %s:%s | %s", + survivor.get("year"), + survivor.get("file"), + survivor.get("line"), + survivor.get("commit"), ) # 4. Write back — snapshots are preserved as-is - with open(json_file, "w", encoding="utf-8") as f: - json.dump({"snapshots": snapshots, "fossils": fossils}, f, separators=(",", ":")) + tmp_file = json_file.with_suffix(f"{json_file.suffix}.tmp") + with open(tmp_file, "w", encoding="utf-8") as f: + json.dump( + {"snapshots": snapshots, "fossils": fossils}, + f, + separators=(",", ":"), + ) + os.replace(tmp_file, json_file) + + logger.info(" ✓ Successfully wrote fossils for %s", repo_name) - logger.info(f" ✓ Successfully wrote fossils for {repo_name}") + except Exception as e: # pylint: disable=broad-exception-caught + logger.error(" ✗ Error computing fossils for %s: %s", repo_name, e) + had_failures = True - except Exception as e: - logger.error(f" ✗ Error computing fossils for {repo_name}: {e}") + return had_failures + + +# --------------------------------------------------------------------------- +# Incremental survivor-only update (used by GitHub Actions) +# --------------------------------------------------------------------------- + + +def update_survivor_fossils(data_dir, repo_urls): + # pylint: disable=too-many-locals,too-many-branches,too-many-statements + """ + Refresh only the Survivor (Living) fossil for each repo. + Skips writing to disk if the fossil's file:line:commit hasn't changed. + + This is designed to be fast and run on every monthly cron tick so that + the living fossil stays current even when no new snapshots are being added. + + Returns the number of repos where the survivor was updated. + """ + data_path = Path(data_dir) + temp_dir = Path("./temp_fossil_repos") + temp_dir.mkdir(exist_ok=True) + + updated_count = 0 + had_failures = False + + for json_file in sorted(data_path.glob("*.json")): + if json_file.name == "manifest.json": + continue + + repo_name = json_file.stem.replace("_data", "") + repo_url = repo_urls.get(repo_name) + + if not repo_url: + logger.warning("No URL found for '%s', skipping.", repo_name) + continue + + logger.info("━━━ Checking survivor for: %s ━━━", repo_name) + + # 1. Load existing data + with open(json_file, "r", encoding="utf-8") as f: + raw_data = json.load(f) + + if isinstance(raw_data, list): + snapshots = raw_data + existing_fossils = {} + else: + snapshots = raw_data.get("snapshots", []) + existing_fossils = raw_data.get("fossils", {}) + + if not snapshots: + logger.warning(" No snapshots found in %s, skipping.", json_file.name) + continue + + existing_survivor = existing_fossils.get("survivor", {}) + + # 2. Clone or fetch the repo + local_repo = temp_dir / repo_name + if not local_repo.exists(): + logger.info(" Cloning %s...", repo_url) + _run_command(["git", "clone", repo_url, str(local_repo)]) + else: + logger.info(" Fetching latest...") + try: + _run_command(["git", "fetch", "--all"], cwd=local_repo) + except RuntimeError as e: + logger.warning(" Fetch failed (continuing with local): %s", e) + + # 3. Compute new survivor + try: + new_survivor = get_survivor_fossil(local_repo) + + old_identity = _fossil_identity(existing_survivor) + new_identity = _fossil_identity(new_survivor) + metadata_changed = existing_survivor.get("view_commit") != new_survivor.get( + "view_commit" + ) + + if old_identity == new_identity and not metadata_changed: + logger.info( + " ✓ Survivor unchanged: %s:%s (commit %s) — skipping write.", + new_survivor.get("file"), + new_survivor.get("line"), + new_survivor.get("commit"), + ) + continue + + # Something changed — log the diff clearly + logger.info(" ↻ Survivor updated for %s:", repo_name) + logger.info( + " OLD: %s:%s @ %s", + existing_survivor.get("file"), + existing_survivor.get("line"), + existing_survivor.get("commit"), + ) + logger.info( + " NEW: %s:%s @ %s", + new_survivor.get("file"), + new_survivor.get("line"), + new_survivor.get("commit"), + ) + + # 4. Write back — genesis is preserved, only survivor is replaced + updated_fossils = {**existing_fossils, "survivor": new_survivor} + tmp_file = json_file.with_suffix(f"{json_file.suffix}.tmp") + with open(tmp_file, "w", encoding="utf-8") as f: + json.dump( + {"snapshots": snapshots, "fossils": updated_fossils}, + f, + separators=(",", ":"), + ) + os.replace(tmp_file, json_file) + + logger.info(" ✓ Wrote updated survivor for %s", repo_name) + updated_count += 1 + + except Exception as e: # pylint: disable=broad-exception-caught + logger.error(" ✗ Error updating survivor for %s: %s", repo_name, e) + had_failures = True + + logger.info("\nSurvivor update complete. %d repo(s) updated.", updated_count) + return had_failures # --------------------------------------------------------------------------- # Entry point # --------------------------------------------------------------------------- -if __name__ == "__main__": - REPO_URLS = { - "react": "https://github.com/facebook/react.git", - "numpy": "https://github.com/numpy/numpy.git", - "langchain": "https://github.com/langchain-ai/langchain.git", - "zed": "https://github.com/zed-industries/zed.git", - "claude-code": "https://github.com/anthropics/claude-code.git", + +def main(): + # pylint: disable=duplicate-code + """ + Main entry point for fossil backfill and incremental survivor checking. + """ + config_path = "theseus.config.json" + if not os.path.exists(config_path): + logger.error("Configuration file not found: %s", config_path) + sys.exit(1) + + with open(config_path, "r", encoding="utf-8") as f: + config = json.load(f) + + data_dir = config.get("dataDir", "./data") + + # Build dynamically from config: name -> github URL + repo_urls = { + repo["name"]: f"https://github.com/{repo['repo']}.git" + for repo in config.get("repositories", []) + if "name" in repo and "repo" in repo } - backfill_fossils("./data", REPO_URLS) + + if not repo_urls: + logger.error("No valid repositories found in configuration.") + sys.exit(1) + + parser = argparse.ArgumentParser( + description="Manage fossil data for Theseus repos." + ) + parser.add_argument( + "--only", + metavar="REPO", + help=f"Process only this repo. Choices: {', '.join(repo_urls)}", + ) + parser.add_argument( + "--update-survivor", + action="store_true", + help=( + "Incremental mode: only refresh the Survivor (Living) fossil. " + "Skips writing if file:line:commit hasn't changed. " + "Genesis is left untouched. Used by GitHub Actions." + ), + ) + args = parser.parse_args() + + if args.only: + if args.only not in repo_urls: + parser.error( + f"Unknown repo '{args.only}'. Valid options: {', '.join(repo_urls)}" + ) + selected = {args.only: repo_urls[args.only]} + logger.info("Running for single repo: %s", args.only) + else: + selected = repo_urls + + if args.update_survivor: + logger.info("Mode: incremental survivor update") + had_failures = update_survivor_fossils(data_dir, selected) + else: + logger.info("Mode: full backfill (genesis + survivor)") + had_failures = backfill_fossils(data_dir, selected) + + if had_failures: + logger.error("One or more repositories failed to update. Exiting non-zero.") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/scripts/analyse_repository.py b/scripts/analyse_repository.py index d6a832e..5f6ac07 100644 --- a/scripts/analyse_repository.py +++ b/scripts/analyse_repository.py @@ -2,6 +2,8 @@ This script is responsible for doing the heavy lifting. Processes repository snapshots incrementally to track code age distribution. Uses quarterly resolution for historical data (pre-2025) and monthly for recent data (2025+). + +Fossil computation is handled separately by add_fossils.py. """ import concurrent.futures @@ -11,11 +13,11 @@ import shutil import stat import subprocess +import sys import time from collections import defaultdict from datetime import datetime, timezone from itertools import groupby -from typing import Optional logger = logging.getLogger(__name__) @@ -160,16 +162,12 @@ def analyze_snapshots(repo_path: str, commit_hash: str) -> dict[str, int]: valid_files = [f for f in files if os.path.isfile(os.path.join(repo_path, f))] # Safe BLAME_WORKERS parsing with fallback - default_workers = min(20, (os.cpu_count() or 1) * 2) + max_workers = min(20, (os.cpu_count() or 1) * 2) try: - env_workers = os.environ.get("BLAME_WORKERS") - if env_workers is not None: - parsed = int(env_workers) - max_workers = max(1, min(parsed, 100)) # Clamp between 1-100 - else: - max_workers = default_workers + if "BLAME_WORKERS" in os.environ: + max_workers = max(1, min(int(os.environ["BLAME_WORKERS"]), 100)) except ValueError: - max_workers = default_workers + pass with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: future_to_file = { @@ -205,67 +203,8 @@ def load_existing_state(json_fname: str) -> dict: return {"snapshots": [], "fossils": {}} -def _get_fossil_metadata(repo_path: str, commit_hash: str) -> dict: - """ - Find the oldest line in the repository at a specific commit. - """ - _run_command(["git", "checkout", commit_hash], cwd=repo_path) - files_output = _run_command(["git", "ls-files"], cwd=repo_path) - files = [ - f - for f in files_output.splitlines() - if os.path.isfile(os.path.join(repo_path, f)) - ] - - oldest_fossil = { - "timestamp": 2147483647, - "file": "", - "content": "", - "year": "", - "commit": "", - "line": 0, - } - - for file in files: - try: - blame_output = _run_command( - ["git", "blame", "--line-porcelain", file], cwd=repo_path - ) - current_commit_data = {} - line_num = 0 - - for line in blame_output.splitlines(): - if line.startswith("\t"): - line_num += 1 - timestamp = current_commit_data.get("author-time") - if timestamp and timestamp < oldest_fossil["timestamp"]: - oldest_fossil["timestamp"] = timestamp - oldest_fossil["file"] = file - oldest_fossil["content"] = line.lstrip("\t").strip() - oldest_fossil["year"] = datetime.fromtimestamp( - timestamp, timezone.utc - ).strftime("%Y") - oldest_fossil["commit"] = current_commit_data.get("commit", "")[ - :7 - ] - oldest_fossil["line"] = line_num - else: - if line and line[0] != "\t": - parsed_commit_hash = line.split(" ")[0] - if len(parsed_commit_hash) == 40: - current_commit_data["commit"] = parsed_commit_hash - elif line.startswith("author-time "): - parts = line.split(" ") - if len(parts) >= 2: - current_commit_data["author-time"] = int(parts[1]) - except Exception: - continue - - return oldest_fossil - - def _atomic_write_json( - json_path: str, snapshots: list[dict], fossils: Optional[dict] = None + json_path: str, snapshots: list[dict], fossils: dict | None = None ) -> None: """Write JSON data atomically and minified to prevent corruption and save space.""" tmp_path = json_path + ".tmp" @@ -276,6 +215,7 @@ def _atomic_write_json( def process_repository(repo_slug: str, data_dir: str) -> None: + # pylint: disable=too-many-locals,too-many-branches,too-many-statements """ Orchestrate the extraction of Ship of Theseus code persistence data using an incremental load strategy by just processing the delta. @@ -283,6 +223,8 @@ def process_repository(repo_slug: str, data_dir: str) -> None: Processes year-by-year and writes to disk after each year completes to prevent data loss on crash. + Fossil data is NOT touched here — that is handled by add_fossils.py. + :param repo_slug: The GitHub repository identifier (e.g., 'facebook/react'). :param data_dir: Path where the resulting JSON data will be saved. """ @@ -308,7 +250,8 @@ def process_repository(repo_slug: str, data_dir: str) -> None: state = load_existing_state(output_json_path) historical_snapshots = state["snapshots"] - fossils = state["fossils"] + # Preserve any existing fossil data — do not touch it + existing_fossils = state.get("fossils", {}) processed_periods = set(item["snapshot_date"] for item in historical_snapshots) all_snapshots = get_snapshots(temp_repo_path) @@ -382,29 +325,8 @@ def process_repository(repo_slug: str, data_dir: str) -> None: final_snapshots = historical_snapshots + total_new_data final_snapshots.sort(key=lambda x: x["snapshot_date"]) - # Updated Fossil Stage: Logic for Genesis vs Survivor - # Recalculate if fossils are missing or missing key fields (commit, file, line) - needs_genesis = not fossils.get("genesis", {}).get("commit") - needs_survivor = not fossils.get("survivor", {}).get("commit") - - if final_snapshots and (needs_genesis or needs_survivor): - logger.info( - "[%s] Computing targeted fossils (Genesis and Survivor)", repo_name - ) - - if needs_genesis: - genesis_commit = all_snapshots[0][1] - fossils["genesis"] = _get_fossil_metadata( - temp_repo_path, genesis_commit - ) - - if needs_survivor: - survivor_commit = all_snapshots[-1][1] - fossils["survivor"] = _get_fossil_metadata( - temp_repo_path, survivor_commit - ) - - _atomic_write_json(output_json_path, final_snapshots, fossils) + # Write snapshot data, preserving existing fossil data untouched + _atomic_write_json(output_json_path, final_snapshots, existing_fossils) logger.info( "[%s] Completed year %s in %.2f seconds. Wrote %d snapshots to disk.", @@ -419,7 +341,7 @@ def process_repository(repo_slug: str, data_dir: str) -> None: logger.info("Cleaning up temporary directory: %s", temp_repo_path) time.sleep(1) - def handle_remove_readonly(func, path, exc_info): + def handle_remove_readonly(func, path, _exc_info): """Handle permission errors on Windows/Unix by adding write permission.""" try: current_mode = os.stat(path).st_mode @@ -429,14 +351,14 @@ def handle_remove_readonly(func, path, exc_info): func(path) except PermissionError as e: logger.warning("Permission error cleaning up %s: %s", path, e) - except Exception as e: + except Exception as e: # pylint: disable=broad-exception-caught logger.warning("Error cleaning up %s: %s", path, e) for attempt in range(3): try: - shutil.rmtree(temp_repo_path, onerror=handle_remove_readonly) + shutil.rmtree(temp_repo_path, onexc=handle_remove_readonly) break - except Exception as e: + except Exception as e: # pylint: disable=broad-exception-caught if attempt < 2: time.sleep(1) logger.warning("Cleanup attempt %d failed: %s", attempt + 1, e) @@ -447,23 +369,34 @@ def handle_remove_readonly(func, path, exc_info): ) -if __name__ == "__main__": +def main(): + """ + Main entry point. Loads configuration, creates output directory, + and runs the repository analysis pipeline for all specified targets. + """ logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) - DATA_OUTPUT_DIR = "./data" + config_path = "theseus.config.json" + if not os.path.exists(config_path): + logger.error("Configuration file not found: %s", config_path) + sys.exit(1) + + with open(config_path, "r", encoding="utf-8") as f: + config = json.load(f) + + DATA_OUTPUT_DIR = config.get("dataDir", "./data") os.makedirs(DATA_OUTPUT_DIR, exist_ok=True) TARGETS = [ - "anthropics/claude-code", - "facebook/react", - "langchain-ai/langchain", - "zed-industries/zed", - "numpy/numpy", + repo["repo"] for repo in config.get("repositories", []) if "repo" in repo ] + if not TARGETS: + logger.error("No valid repositories found in configuration.") + sys.exit(1) # Bound top-level workers by CPU count max_top_level_workers = min( @@ -484,8 +417,12 @@ def handle_remove_readonly(func, path, exc_info): target = futures[future] try: future.result() - except Exception as e: + except Exception as e: # pylint: disable=broad-exception-caught logger.error("Failed to process %s: %s", target, e) overall_elapsed = time.perf_counter() - overall_start logger.info("TOTAL PIPELINE EXECUTION TIME: %.2f seconds", overall_elapsed) + + +if __name__ == "__main__": + main() diff --git a/scripts/cleanup_data.py b/scripts/cleanup_data.py index 70a0a7e..b11c3dd 100644 --- a/scripts/cleanup_data.py +++ b/scripts/cleanup_data.py @@ -1,5 +1,8 @@ +""" +Module for cleaning up and minifying past snapshot data JSONs. +""" + import json -import os from pathlib import Path diff --git a/style.css b/style.css index e0cc5f6..a2999ea 100644 --- a/style.css +++ b/style.css @@ -53,7 +53,8 @@ body { display: flex; flex-direction: column; align-items: center; - gap: 1.5rem; /* Reduced gap to accommodate badge */ + gap: 1.5rem; + /* Reduced gap to accommodate badge */ } .author-badge { @@ -217,16 +218,145 @@ body { display: flex; flex-direction: column; padding-bottom: 3rem; + position: relative; } -.loading-state { +/* ─── Skeleton Loader ─────────────────────────────────────────────────────── */ +@keyframes shimmer { + 0% { background-position: -1000px 0; } + 100% { background-position: 1000px 0; } +} + +.skeleton-loading-overlay { position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - font-size: 1.2rem; - animation: pulse 2s infinite; + inset: 0; + z-index: 10; + display: flex; + flex-direction: column; + gap: 1.25rem; + padding: 1.5rem 1rem 1rem; + background: transparent; + pointer-events: auto; +} + +.skeleton-loading-overlay > * { + pointer-events: none; +} + +/* Shimmer base — applied to every skeleton element */ +.skeleton-pill, +.skeleton-tick, +.skeleton-wave { + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0.04) 0%, + rgba(255, 255, 255, 0.10) 40%, + rgba(59, 199, 199, 0.08) 55%, + rgba(255, 255, 255, 0.04) 100% + ); + background-size: 1000px 100%; + animation: shimmer 1.8s infinite linear; + border-radius: 9999px; +} + +/* Legend row */ +.skeleton-legend { + display: flex; + justify-content: center; + gap: 2rem; + padding-bottom: 1rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.04); +} + +.skeleton-pill { + height: 14px; + border-radius: 9999px; +} +.skeleton-pill:nth-child(1) { width: 60px; animation-delay: 0s; } +.skeleton-pill:nth-child(2) { width: 80px; animation-delay: 0.15s; } +.skeleton-pill:nth-child(3) { width: 70px; animation-delay: 0.3s; } +.skeleton-pill:nth-child(4) { width: 65px; animation-delay: 0.45s; } + +/* Chart body */ +.skeleton-chart { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.skeleton-chart-inner { + flex: 1; + display: flex; + gap: 0.75rem; + min-height: 400px; +} + +/* Y-axis ticks */ +.skeleton-y-axis { + width: 40px; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-end; + padding: 0.25rem 0; +} + +.skeleton-tick { + height: 10px; + border-radius: 4px; +} +.skeleton-y-axis .skeleton-tick { width: 32px; } +.skeleton-y-axis .skeleton-tick:nth-child(1) { animation-delay: 0.0s; } +.skeleton-y-axis .skeleton-tick:nth-child(2) { animation-delay: 0.1s; } +.skeleton-y-axis .skeleton-tick:nth-child(3) { animation-delay: 0.2s; } +.skeleton-y-axis .skeleton-tick:nth-child(4) { animation-delay: 0.3s; } +.skeleton-y-axis .skeleton-tick:nth-child(5) { animation-delay: 0.4s; } + +/* Stacked area mock */ +.skeleton-area { + flex: 1; + display: flex; + flex-direction: column; + justify-content: flex-end; + gap: 0; + border-radius: 0.75rem; + overflow: hidden; + position: relative; +} + +.skeleton-wave { + border-radius: 0; + animation-timing-function: ease-in-out; +} +.skeleton-wave.wave-1 { flex: 2; animation-delay: 0.05s; opacity: 0.5; } +.skeleton-wave.wave-2 { flex: 3; animation-delay: 0.2s; opacity: 0.7; } +.skeleton-wave.wave-3 { flex: 4; animation-delay: 0.35s; opacity: 1; } + +/* X-axis ticks */ +.skeleton-x-axis { + display: flex; + justify-content: space-between; + padding: 0 44px 0 48px; } +.skeleton-x-axis .skeleton-tick.short { + width: 30px; + height: 10px; +} +.skeleton-x-axis .skeleton-tick:nth-child(1) { animation-delay: 0.0s; } +.skeleton-x-axis .skeleton-tick:nth-child(2) { animation-delay: 0.1s; } +.skeleton-x-axis .skeleton-tick:nth-child(3) { animation-delay: 0.2s; } +.skeleton-x-axis .skeleton-tick:nth-child(4) { animation-delay: 0.3s; } +.skeleton-x-axis .skeleton-tick:nth-child(5) { animation-delay: 0.4s; } +.skeleton-x-axis .skeleton-tick:nth-child(6) { animation-delay: 0.5s; } + +/* Keep pulse for any legacy use */ +@keyframes pulse { + 0% { opacity: 0.3; } + 50% { opacity: 0.8; } + 100% { opacity: 0.3; } +} + .error-banner { background: rgba(153, 27, 27, 0.2); @@ -242,20 +372,6 @@ body { display: none !important; } -@keyframes pulse { - 0% { - opacity: 0.3; - } - - 50% { - opacity: 0.8; - } - - 100% { - opacity: 0.3; - } -} - /* Chart Canvas */ .chart-wrapper { flex: 1; @@ -669,9 +785,10 @@ svg#main-chart { background: rgba(255, 255, 255, 0.02); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 1.5rem; - padding: 2rem; + padding: 1.25rem 1.5rem; position: relative; overflow: hidden; + text-align: left; } .fossil-card::before { @@ -682,7 +799,7 @@ svg#main-chart { right: 0; bottom: 0; background: radial-gradient(ellipse at top left, rgba(59, 199, 199, 0.08), transparent 50%), - radial-gradient(ellipse at bottom right, rgba(240, 163, 59, 0.08), transparent 50%); + radial-gradient(ellipse at bottom right, rgba(240, 163, 59, 0.08), transparent 50%); pointer-events: none; opacity: 0.6; } @@ -738,19 +855,24 @@ svg#main-chart { background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 0.75rem; - padding: 1.25rem; + padding: 0.6rem 1rem; font-family: var(--font-mono); font-size: 0.85rem; color: var(--text-primary); - opacity: 0.5; - line-height: 1.5; - word-break: break-all; - white-space: pre-wrap; - position: relative; + opacity: 0.8; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: flex; + align-items: center; + gap: 0.5rem; + text-align: left; } .fossil-code::before { - content: '> '; + content: '>'; color: var(--accent-cyan); opacity: 0.5; + flex-shrink: 0; + display: inline; } diff --git a/tests/test_analyse_repository.py b/tests/test_analyse_repository.py index 2e4b547..efcc66a 100644 --- a/tests/test_analyse_repository.py +++ b/tests/test_analyse_repository.py @@ -1,22 +1,23 @@ +""" +Tests for the analyse repository module. +""" + import json import os import sys import tempfile from datetime import datetime, timezone -import pytest - sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from scripts.analyse_repository import ( - _parse_blame_output, - load_existing_state, -) +# pylint: disable=wrong-import-position,import-error +from scripts.analyse_repository import _parse_blame_output, load_existing_state class TestParseBlameOutput: """Tests for the git blame output parser.""" def test_single_file_single_author_year(self): + """Test parsing a blame output with a single commit and author.""" blame_output = ( "abc123def4567890123456789012345678901234 1 1 1\n" "author Test Author\n" @@ -29,6 +30,7 @@ def test_single_file_single_author_year(self): assert result == {year: 1} def test_multiple_commits_different_years(self): + """Test parsing a blame output with multiple commits stretching across different years.""" blame_output = ( "abc123def4567890123456789012345678901234 1 1 1\n" "author Test Author\n" @@ -48,6 +50,7 @@ def test_multiple_commits_different_years(self): assert result[year_2024] == 1 def test_lines_attributed_to_correct_year(self): + """Test parsing a blame output where multiple lines are credited to the same commit and year.""" blame_output = ( "abc123def4567890123456789012345678901234 1 1 1\n" "author Test Author\n" @@ -62,10 +65,12 @@ def test_lines_attributed_to_correct_year(self): assert result[year] == 3 def test_empty_output(self): + """Test parsing an empty blame output.""" result = _parse_blame_output("") assert result == {} def test_invalid_timestamp_ignored(self): + """Test parsing a blame output that contains an invalid timestamp, ensuring it is handled properly.""" blame_output = ( "abc123def4567890123456789012345678901234 1 1 1\n" "author Test Author\n" @@ -77,6 +82,7 @@ def test_invalid_timestamp_ignored(self): assert result == {} def test_40_and_64_char_hashes(self): + """Test parsing a blame output safely using varied hash sizes.""" blame_output = ( "abc123def4567890123456789012345678901234 1 1 1\n" "author Test Author\n" @@ -93,6 +99,7 @@ class TestLoadExistingState: """Tests for loading existing JSON state.""" def test_load_valid_json(self): + """Test loading a correctly formatted existing JSON state.""" data = [ { "snapshot_date": "2024-01", @@ -110,21 +117,27 @@ def test_load_valid_json(self): f.flush() result = load_existing_state(f.name) - assert len(result) == 2 - assert result[0]["snapshot_date"] == "2024-01" + # load_existing_state always returns {"snapshots": [...], "fossils": {}} + assert "snapshots" in result + assert "fossils" in result + snapshots = result["snapshots"] + assert len(snapshots) == 2 + assert snapshots[0]["snapshot_date"] == "2024-01" os.unlink(f.name) def test_file_not_exists(self): + """Test loading state when the requested file does not exist, expecting a blank default structure.""" result = load_existing_state("/nonexistent/path/data.json") - assert result == [] + assert result == {"snapshots": [], "fossils": {}} def test_corrupted_json_returns_empty(self): + """Test loading a corrupted JSON file, which should fallback dynamically to a default parsed object.""" with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: f.write("not valid json {") f.flush() result = load_existing_state(f.name) - assert result == [] + assert result == {"snapshots": [], "fossils": {}} os.unlink(f.name) diff --git a/tests/test_data_integrity.py b/tests/test_data_integrity.py index b71b424..523c5db 100644 --- a/tests/test_data_integrity.py +++ b/tests/test_data_integrity.py @@ -1,6 +1,9 @@ +""" +Tests data integrity and schema validation for Theseus generated JSON files. +""" + import json from pathlib import Path -import pytest def test_data_integrity_optimized_schema(): @@ -57,22 +60,22 @@ def test_data_integrity_optimized_schema(): continue if not fossil_data: # Empty fossil object is OK continue - assert "year" in fossil_data, ( - f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'year' field" - ) - assert "content" in fossil_data, ( - f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'content' field" - ) + assert ( + "year" in fossil_data + ), f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'year' field" + assert ( + "content" in fossil_data + ), f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'content' field" # New required fields: commit, line, file - assert "file" in fossil_data, ( - f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'file' field" - ) - assert "commit" in fossil_data, ( - f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'commit' field" - ) - assert "line" in fossil_data, ( - f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'line' field" - ) + assert ( + "file" in fossil_data + ), f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'file' field" + assert ( + "commit" in fossil_data + ), f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'commit' field" + assert ( + "line" in fossil_data + ), f"Error in {json_file.name}: Fossil '{fossil_type}' missing 'line' field" if __name__ == "__main__": diff --git a/theseus.config.json b/theseus.config.json new file mode 100644 index 0000000..ec4968d --- /dev/null +++ b/theseus.config.json @@ -0,0 +1,35 @@ +{ + "dataDir": "data", + "repositories": [ + { + "name": "langchain", + "file": "langchain_data.json", + "description": "Framework for developing LLM-driven applications and agents.", + "repo": "langchain-ai/langchain" + }, + { + "name": "react", + "file": "react_data.json", + "description": "Component-based JavaScript library for building user interfaces.", + "repo": "facebook/react" + }, + { + "name": "numpy", + "file": "numpy_data.json", + "description": "The fundamental package for scientific computing in Python.", + "repo": "numpy/numpy" + }, + { + "name": "zed", + "file": "zed_data.json", + "description": "High-performance, GPU-accelerated code editor for teamwork.", + "repo": "zed-industries/zed" + }, + { + "name": "claude-code", + "file": "claude-code_data.json", + "description": "Claude's agentic CLI tool for local coding tasks.", + "repo": "anthropics/claude-code" + } + ] +}