From 48c0d8f489cf430fdbea9b6e452815c4a28a377a Mon Sep 17 00:00:00 2001 From: Oluwarotimi H Quadri Date: Thu, 11 Sep 2025 19:09:06 -0400 Subject: [PATCH 1/4] Create \src --- "\\src" | 1 + 1 file changed, 1 insertion(+) create mode 100644 "\\src" diff --git "a/\\src" "b/\\src" new file mode 100644 index 0000000..a57582c --- /dev/null +++ "b/\\src" @@ -0,0 +1 @@ +/src From af8e65b54f70a126492b3efa058a9c0dc8a46490 Mon Sep 17 00:00:00 2001 From: Oluwarotimi H Quadri Date: Thu, 11 Sep 2025 19:35:33 -0400 Subject: [PATCH 2/4] Delete \src --- "\\src" | 1 - 1 file changed, 1 deletion(-) delete mode 100644 "\\src" diff --git "a/\\src" "b/\\src" deleted file mode 100644 index a57582c..0000000 --- "a/\\src" +++ /dev/null @@ -1 +0,0 @@ -/src From b17788b250fef60535a638f2d11ab2b631aea18b Mon Sep 17 00:00:00 2001 From: Oluwarotimi Quadri Date: Thu, 11 Sep 2025 19:44:07 -0400 Subject: [PATCH 3/4] Add dirs --- src/acmecli/cli.py | 0 src/acmecli/reporting.py | 0 src/acmecli/scoring.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/acmecli/cli.py create mode 100644 src/acmecli/reporting.py create mode 100644 src/acmecli/scoring.py diff --git a/src/acmecli/cli.py b/src/acmecli/cli.py new file mode 100644 index 0000000..e69de29 diff --git a/src/acmecli/reporting.py b/src/acmecli/reporting.py new file mode 100644 index 0000000..e69de29 diff --git a/src/acmecli/scoring.py b/src/acmecli/scoring.py new file mode 100644 index 0000000..e69de29 From 66103d4f2ad9aac59e3f39a2b50e8cee4920dbe6 Mon Sep 17 00:00:00 2001 From: Oluwarotimi Quadri Date: Thu, 11 Sep 2025 20:48:06 -0400 Subject: [PATCH 4/4] feat: add working run test command with pytest-cov parsing --- .coverage | Bin 0 -> 53248 bytes README.md | 1 - pyproject.toml | 11 +++ src/acmecli/reporting.py => python | 0 run | 49 ++++++++++++++ run.py | 1 + src/acmecli.egg-info/PKG-INFO | 4 ++ src/acmecli.egg-info/SOURCES.txt | 15 ++++ src/acmecli.egg-info/dependency_links.txt | 1 + src/acmecli.egg-info/top_level.txt | 1 + src/acmecli/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 144 bytes src/acmecli/__pycache__/types.cpython-313.pyc | Bin 0 -> 4449 bytes src/acmecli/cli.py | 42 ++++++++++++ src/acmecli/metrics/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 152 bytes .../metrics/__pycache__/base.cpython-313.pyc | Bin 0 -> 591 bytes .../license_metric.cpython-313.pyc | Bin 0 -> 1364 bytes src/acmecli/metrics/base.py | 7 ++ src/acmecli/metrics/license_metric.py | 18 +++++ src/acmecli/reporter.py | 6 ++ src/acmecli/scoring.py | 26 +++++++ src/acmecli/types.py | 64 ++++++++++++++++++ ...rics_contract.cpython-313-pytest-8.4.2.pyc | Bin 0 -> 3326 bytes ...porter_schema.cpython-313-pytest-8.4.2.pyc | Bin 0 -> 2003 bytes tests/test_metrics_contract.py | 11 +++ tests/test_reporter_schema.py | 16 +++++ 27 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 .coverage delete mode 100644 README.md create mode 100644 pyproject.toml rename src/acmecli/reporting.py => python (100%) create mode 100644 run create mode 100644 run.py create mode 100644 src/acmecli.egg-info/PKG-INFO create mode 100644 src/acmecli.egg-info/SOURCES.txt create mode 100644 src/acmecli.egg-info/dependency_links.txt create mode 100644 src/acmecli.egg-info/top_level.txt create mode 100644 src/acmecli/__init__.py create mode 100644 src/acmecli/__pycache__/__init__.cpython-313.pyc create mode 100644 src/acmecli/__pycache__/types.cpython-313.pyc create mode 100644 src/acmecli/metrics/__init__.py create mode 100644 src/acmecli/metrics/__pycache__/__init__.cpython-313.pyc create mode 100644 src/acmecli/metrics/__pycache__/base.cpython-313.pyc create mode 100644 src/acmecli/metrics/__pycache__/license_metric.cpython-313.pyc create mode 100644 src/acmecli/metrics/base.py create mode 100644 src/acmecli/metrics/license_metric.py create mode 100644 src/acmecli/reporter.py create mode 100644 src/acmecli/types.py create mode 100644 tests/__pycache__/test_metrics_contract.cpython-313-pytest-8.4.2.pyc create mode 100644 tests/__pycache__/test_reporter_schema.cpython-313-pytest-8.4.2.pyc create mode 100644 tests/test_metrics_contract.py create mode 100644 tests/test_reporter_schema.py diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..ac170622d430d895624681c89b1911e8c06d184a GIT binary patch literal 53248 zcmeI)O>Y}T7zglOukE$-;w_ZQilUOaKoT`c>r`q*dw{gfp`uV;3IY+~uGiyaiM{LY zuG2&nLT-^NRYHP0;#*LTd;t*R!nGU#l>?P3fe>a702T;900I#Be+zWq9Mj7a6YABE1GCnY zfoEFMi=M|%zqPz{VOd;QI(2SYMEk_dh@fSDUMvaEy)4?&6Aim5h3(XB%M5I1RRn7? zN!{^foo{rsk8X9Ga3PH6wW?jGs6eh#7H!XNnckLIms@jTg7|EK+zg@}l!>%g9Zn%; z;_zAF$%gc#V@W?M#f)8_9htrM$%tOwzhAvB!#bIsML(khwV@ros+?_5MwaWJFs0x$W3W=0$Ja+f^&?PEQHrz8_Me< zSLk9C8K0|9rx(0SWSl$>ZcyPIH(~7s@~J5}EVI*T)rkC~POHrqha#dA2kx-QVX=W5 zS2p#v+2vaH>N{{b6LP+yQB=pVFLmVh#h(+s$hoi@jyor~64U}-7|oYo+OI0qD&?ks zsY&m*s%9r}!|f_v{Az`s#y)+fpqHnn)a#eRhQd76njQGtE5(6Ev%Pyr)8XU8+YR@U z!A8QpsM!>5BkVF9EuxDs-=r|U46`$*1xf{|mSLH%{G#VBjcSEA4Qz_=l%TvRe&UO>1R<>71x)XW5JUOXe zEr!h~zFP5%KG1}cE4j-snjC6L)9-l6K=QCbGKuJol1U$lk5F~y^?Z40a#(fZbk$0{ zQt0Ygy?o$++KqYyk4yZ>4OD{n^t!+UH-hxVZ=1oI*jaYoO$DFM<;y1z3@bPnp<3bR z{Iw499=6&4q{g3)Jb7{zWUTmg&^v`xW1Yqg-k1=g!IJ zi3Sl06w}lxed?kQtD*bV%)X;?5Y2GgY5%>Mb)V zcD$JxXe19D?6sHkG~m*^o&I*hc6}VY8J&R#x9AGiB84PX{9b2dmM8eCaZjNiED(SI z1Rwwb2tWV=5P$##AOHaf>^%WZ&8Rxh|1-wVigAaYus{F;5P$##AOHafKmY;|fB*y_ z@Kg$nWwe7v@)sUQ_Nm(BMEo}Z#}_I`7LJWjtun@K#kg(!^;A|6g@ynGAOHafKmY;| zfB*y_009Ue3D@5P$## zAOHafKmY;|fB*y_0D++bm4c?&r%$ZB*e=8}m!2Ut3=BJ!{3ZTGDFT zD?a^)8~O(~^o9LJT~pT6gm|*;dV%yPW~Z_Et6#5e!}2tWV=5P$##AOHafKmY;|P?SZ@R@2O0 zd-MOoQEfd{NZ9NP<2&a6xg(lPl|wTB&mPzOQ~}8;UX+PyN{F+g7 zYPMJ`sr>nW&bXx*_l!S{KWP5{+ml^Elnw$AfB*y_009U<00Izz00bZafgJ*Qn(V9D zT=Vb$ z1w~aL009U<00Izz00bZa0SG_<0=o$C_x~~f-vtJ{AOHafKmY;|fB*y_009U<00MhR F;6HV~bx;5R literal 0 HcmV?d00001 diff --git a/README.md b/README.md deleted file mode 100644 index 4fa56d4..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -# Dev-ACME \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1316de8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "acmecli" +version = "0.0.1" +requires-python = ">=3.10" + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/src/acmecli/reporting.py b/python similarity index 100% rename from src/acmecli/reporting.py rename to python diff --git a/run b/run new file mode 100644 index 0000000..6cacf51 --- /dev/null +++ b/run @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +import sys, subprocess, re, os + +def do_install(): + subprocess.check_call([sys.executable, "-m", "pip", "install", "-e", "."]) + return 0 + +def do_test(): + import re, subprocess, sys, os, pathlib + + # Always run from repo root + os.chdir(pathlib.Path(__file__).parent.resolve()) + + cmd = [sys.executable, "-m", "pytest", "tests", + "--maxfail=1", "--disable-warnings", + "--cov=acmecli", "--cov-report=term-missing"] + r = subprocess.run(cmd, text=True, capture_output=True) + out = (r.stdout or "") + (r.stderr or "") + + # Extract counts + collected = re.search(r"collected\s+(\d+)", out) + passed = re.search(r"(\d+)\s+passed", out) + cov = re.search(r"TOTAL\s+.*?(\d+)%", out) + + x = int(passed.group(1)) if passed else 0 + y = int(collected.group(1)) if collected else 0 + z = int(cov.group(1)) if cov else 0 + + print(f"{x}/{y} test cases passed. {z}% line coverage achieved.") + return 0 if r.returncode == 0 else 1 + + +def main(): + if len(sys.argv) < 2: + print("Usage: run install|test|score ") + sys.exit(1) + cmd = sys.argv[1] + if cmd == "install": sys.exit(do_install()) + if cmd == "test": sys.exit(do_test()) + if cmd == "score": + if len(sys.argv) < 3: + print("Usage: run score ") + sys.exit(1) + sys.exit(do_score(sys.argv[2])) + print("Unknown command.") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/run.py b/run.py new file mode 100644 index 0000000..3dc2192 --- /dev/null +++ b/run.py @@ -0,0 +1 @@ +import run diff --git a/src/acmecli.egg-info/PKG-INFO b/src/acmecli.egg-info/PKG-INFO new file mode 100644 index 0000000..f2ba886 --- /dev/null +++ b/src/acmecli.egg-info/PKG-INFO @@ -0,0 +1,4 @@ +Metadata-Version: 2.4 +Name: acmecli +Version: 0.0.1 +Requires-Python: >=3.10 diff --git a/src/acmecli.egg-info/SOURCES.txt b/src/acmecli.egg-info/SOURCES.txt new file mode 100644 index 0000000..7ab9ca9 --- /dev/null +++ b/src/acmecli.egg-info/SOURCES.txt @@ -0,0 +1,15 @@ +pyproject.toml +src/acmecli/__init__.py +src/acmecli/cli.py +src/acmecli/reporter.py +src/acmecli/scoring.py +src/acmecli/types.py +src/acmecli.egg-info/PKG-INFO +src/acmecli.egg-info/SOURCES.txt +src/acmecli.egg-info/dependency_links.txt +src/acmecli.egg-info/top_level.txt +src/acmecli/metrics/__init__.py +src/acmecli/metrics/base.py +src/acmecli/metrics/license_metric.py +tests/test_metrics_contract.py +tests/test_reporter_schema.py \ No newline at end of file diff --git a/src/acmecli.egg-info/dependency_links.txt b/src/acmecli.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/acmecli.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/acmecli.egg-info/top_level.txt b/src/acmecli.egg-info/top_level.txt new file mode 100644 index 0000000..b96d3b9 --- /dev/null +++ b/src/acmecli.egg-info/top_level.txt @@ -0,0 +1 @@ +acmecli diff --git a/src/acmecli/__init__.py b/src/acmecli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/acmecli/__pycache__/__init__.cpython-313.pyc b/src/acmecli/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc5983a657dd2780c0f5094f9c79236dd96f5550 GIT binary patch literal 144 zcmey&%ge<81VR~yGeGoX5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~ipvsFxJacWU< zOi_Mvc3O-}YMHL1v#)DRaZz$iVsdV3a!zJUe0*kJW=VX!UP0w84x8Nkl+v73yCPPg TevtXaAjU^#Mn=XWW*`dydQKqj literal 0 HcmV?d00001 diff --git a/src/acmecli/__pycache__/types.cpython-313.pyc b/src/acmecli/__pycache__/types.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e236bbae12b3743bb76f6f42860460e687967ec GIT binary patch literal 4449 zcmbUkOKcm*b(cHbU4BGK)VFfv%9dS&KrFb3Ttn{Hr4>txOebDCagBAh>*a`|Lvq>K zr4t!F2oPYqH}zmZx%p_{+@5+gkV}pfXvCYjBt${Fy<@&r&nSHBDd7`+|prwl- zz_rB+Qh|G=;aA)`U<4UCg~u;nxj4JXwTl;u7mV2=CwUkHUA;74oV)g0o|?OM>C$}R z(z_S(vpl|dC4XVDn3K7>#N4~I?nFiuf6>vTovSc>D47zI4T;4JnJFhFlbCTMb_zJA z3Ya1=q!EE>fW;9rRHmB=Q#cT4nxN?{Sypoio-Ep|Onsw4oh=Q7xzc1+u6k~hIh2#S zU87ugY@e20wo>3aqjxGEocD>8OL5(@P{6Wy(z0r9saZulZCQV4+SQ2DYgtPb=K0l1 zoz`7oE1u6dQ0;o%^=-7B2UOJvchvLIUhGGp4NIU(Lzd;Cq+{8>&nmZ?K7}c440t>6 zlla^D^X4^=GS6hL^U0EVk={9dA%A(+^q6DXPK`R%is^&zys5?t8-OVS;Ca210PaiQ zCemwjK{~ru2$DT(^Fd~~O@@P`$5-bbEU$Zis(nR{vlE?p|Cfu`yno%i3e0f{FFoUNUwM$@gpMK`^bhYBpx<`c@c-nF6p?zHD@i^EF z{t_kk(|XmlOP(-TA(voB@E~s?7)Njv!H*G4Ai#3Ml?tphi9@thD98@4dmH)n&;C5sCKEv_v$hcQX73fkC2;n;5bJRtTwguhj?*CX zgF+oGB|_bZgQe7*##5ImoZtD-t~Tk`Fs#Ey0Py%7B=BSvLaXkqShXK$3Lakq!})9q zS31lXKSc%t+{^0+0Ffl(C)KyM81xO?yAqxoWez{>KY{aRr!{prw@B>WSP?Xl*n*~` zodqo+Ftk*%OG|ZW=`O8DU{ECXL}sST?d{I!>(ctWv}~6)(4`G_X+ww^!%%LZ;_}2* z+HjeF)%|o!1R(l{jRN3_I`u8jaT(?PUDS$P#?#EMHLPaC@+(k~2KSD-GRa#_&swq_ z-(@_zi+B0i24zbwtJ!sjT2SsQHII+IO6$sNQF8I#PL_4FNqUvLG*ULZi+5)ZCrCL_Vvc$XtYg6gTBFaW8>udjm^FZtQw*0TgTeuSa9N0o16-U#H!Y@zi9yDs+k`-My2=t8!~NU%&f33p*nc!AA<%d3=aVHIOT0JzGi-(+9SQ@VR z*DJ!_hZ&J7>;uG$xXxZpl1tQgmaQ7~Z9ckRhh5Ika8|T?KHObz!1Ya8-E;8ho$nP> z?9o8k)qq`x2_81{9jf8d3OCv+A4n3`rXH^;>Vi>YjgJZFGzF* zT-AYJ_v?l|`tZ=OhYRzx2D%U3pw? z9TLV4?#9XpmH~)$A%LQsQlLh6b_Vq49f?)x%XfCeLF5SE)Y*-QLN4?Dg<}Hdgl`Oy`@-|s12KjG zUC-_yI4n$R$UuM=_ud8o-L+^a$<2+07P0Gb()_onY-`~3W@>6x4-kE4 zGc~=c{#{P4pV*W~{u7JKliL!29YuTwge!O z1?;=zE_X;ix-9{SxPZN!QsgCheeNYpT9RKTWAYXG0gUkfrLM^5A6)-8u+9s6{uheA B_n!a& literal 0 HcmV?d00001 diff --git a/src/acmecli/cli.py b/src/acmecli/cli.py index e69de29..6c5d6d3 100644 --- a/src/acmecli/cli.py +++ b/src/acmecli/cli.py @@ -0,0 +1,42 @@ +import sys +from pathlib import Path +from .types import ReportRow +from .reporter import write_ndjson + +def _classify(url: str) -> str: + u = url.strip().lower() + if "huggingface.co/datasets/" in u: + return "DATASET" + if "github.com/" in u: + return "CODE" + if "huggingface.co/" in u: + return "MODEL" + return "CODE" + +def _stub_row(name: str) -> ReportRow: + zero = 0.0 + return ReportRow( + name=name, category="MODEL", + net_score=zero, net_score_latency=0, + ramp_up_time=zero, ramp_up_time_latency=0, + bus_factor=zero, bus_factor_latency=0, + performance_claims=zero, performance_claims_latency=0, + license=zero, license_latency=0, + size_score={"raspberry_pi":0.0, "jetson_nano":0.0, "desktop_pc":0.0, "aws_server":0.0}, + size_score_latency=0, + dataset_and_code_score=zero, dataset_and_code_score_latency=0, + dataset_quality=zero, dataset_quality_latency=0, + code_quality=zero, code_quality_latency=0 + ) + +def main(argv: list[str]) -> int: + # argv pattern: ["score", "/abs/path/URL_FILE"] + _, url_file = argv + lines = Path(url_file).read_text(encoding="utf-8").splitlines() + for raw in lines: + url = raw.strip() + if not url: + continue + if _classify(url) == "MODEL": + write_ndjson(_stub_row(url)) + return 0 diff --git a/src/acmecli/metrics/__init__.py b/src/acmecli/metrics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/acmecli/metrics/__pycache__/__init__.cpython-313.pyc b/src/acmecli/metrics/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63ad76f244e30b9126f7c00fd73e9926221ec436 GIT binary patch literal 152 zcmey&%ge<81d|dEXMpI(AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl=6XRDad;?$zz zn4sYP3?Ot8v;(kI2nmG(xzbjO(@F?Sk-3=bL2`F`zErBZ zl!>x+>CVdE!NiQZSO}1qxIMbeS z&fpd>U}O^_f}{nk!ko=Y+nmC%8QB>uJ*QMiD|6sbKVofvDCGmz(kg;kg;yCe2lStF0#Q; zqITON-j_P)ZanL-UiYQ;M)L+9%5sd^c1-M;g*k@pnXVc*gI9|%K-Wh9vt4+wsBJ-F z7TpdjXg^GQq9|lG2np7WR5q!l@3q(Yu9B!Y=JC5X{E0lc`?$Te!4<|_#3LCGQ$F8K z@z+Ah)#6YGC=>H|iFm~{@ocxt29Du7z4T${{m$vrbK0Dm2!wU*m4`)|C1_XGt`Sn9 z{bjUu=vxRZR4vRFUF!EmmgS`=(>zoC{%p!;z-61UWIzWGNBLkpl#lSLk@h8O&A`MV pg#3V&pU^pn&aZ3DjgHF4Eg?15(4G^|) z*4%apOAVNXc@Tvpng_2u4@)@!OGfTApldYUb3Uw}b9A$y3-@G-QK*X| z>Lx*~boqk-yD}BKGSpSkA7Cio4U+9xqIOn-a7XG&p7LQ359B$QZW`J}*^N{xG34qK zamUVRm7GKH01W|X3Ii=+qAe`X5w=$lj#m@~&s}syu?`>GSQ4)15>j5YMJYonqKs4B zN^?qA--|+-D4CNucc`*1#yO^K#k7k%6T)FexPUSpU=mKo&8(QWMZZyj)FIr01!`mc z2WZ+xpK$bR1%ek{qU2Hw<;gD-d?%)j;Ap6g0LsUIP!Yg zP?b04<>%*{yL-AA_TyNFsjgk<#Eu{K2MKMEwxT4(5=Ck|33{^W;LCKtYmAubE$uwy z&C}&LNM#c4`8}n$8@`Ldi_cD`bo8MU_QNc z<-j?s&3?UbTEFq6e&eLxIyN8IUq9Y{eD%&r?Tv%-*>wGA@tftZmcNxJ)3+bFx3Nxj zutsXVS=FxZGhN@;W#8}hw+Au%Q@;P{Ac)6brhUH?p-ST@kxAe8vB4F11(64dJVE4U zaF!Jh0T)Q=n(wPLNTbjXQhKis2B}n(&i|dW;J&dsQCvdZCG{CRt-Q3qI None: + REGISTRY.append(metric) diff --git a/src/acmecli/metrics/license_metric.py b/src/acmecli/metrics/license_metric.py new file mode 100644 index 0000000..838708a --- /dev/null +++ b/src/acmecli/metrics/license_metric.py @@ -0,0 +1,18 @@ +import time +from ..types import Metric, Signals, TargetSpec, SourceHandler, Cache, MetricValue +from .base import register + +class LicenseMetric: + name = "license" + + def collect(self, spec: TargetSpec, handler: SourceHandler, cache: Cache) -> Signals: + # Week 1 stub: no network; just return empty signals + return {} + + def score(self, signals: Signals) -> MetricValue: + t0 = time.perf_counter() + value = 0.0 # TODO: map license presence/compatibility → [0,1] + latency_ms = int((time.perf_counter() - t0) * 1000) + return MetricValue(self.name, value, latency_ms) + +register(LicenseMetric()) diff --git a/src/acmecli/reporter.py b/src/acmecli/reporter.py new file mode 100644 index 0000000..a881795 --- /dev/null +++ b/src/acmecli/reporter.py @@ -0,0 +1,6 @@ +import json +from dataclasses import asdict +from .types import ReportRow + +def write_ndjson(row: ReportRow) -> None: + print(json.dumps(asdict(row), ensure_ascii=False)) diff --git a/src/acmecli/scoring.py b/src/acmecli/scoring.py index e69de29..9fcc384 100644 --- a/src/acmecli/scoring.py +++ b/src/acmecli/scoring.py @@ -0,0 +1,26 @@ +from typing import Dict, List +from .types import MetricValue + +# Initial weights (sum ≈ 1.0). Tweak later. +WEIGHTS: Dict[str, float] = { + "ramp_up_time": 0.15, + "bus_factor": 0.15, + "performance_claims": 0.10, + "license": 0.20, + "size": 0.10, # average of size_score dict + "dataset_and_code_score": 0.10, + "dataset_quality": 0.10, + "code_quality": 0.10, +} + +class ScoringEngine: + def compute(self, values: List[MetricValue], size_avg: float = 0.0) -> tuple[float, int]: + lookup = {mv.name: mv for mv in values} + total_latency = sum(mv.latency_ms for mv in values) + net = 0.0 + for k, w in WEIGHTS.items(): + if k == "size": + net += w * float(size_avg) + elif k in lookup: + net += w * float(lookup[k].value) + return (max(0.0, min(1.0, net)), total_latency) diff --git a/src/acmecli/types.py b/src/acmecli/types.py new file mode 100644 index 0000000..32ec282 --- /dev/null +++ b/src/acmecli/types.py @@ -0,0 +1,64 @@ +from dataclasses import dataclass, asdict +from typing import Protocol, Iterable, TypedDict, Literal, Optional, Dict + +Category = Literal["MODEL", "DATASET", "CODE"] +Source = Literal["GITHUB", "HUGGINGFACE", "LOCAL"] + +@dataclass(frozen=True) +class TargetSpec: + url: str + source: Source + name: str + category: Category + revision: Optional[str] = None # commit/tag if known + +class Signals(TypedDict, total=False): + readme_text: str + license_name: str + contributors: Dict[str, int] + stars: int + downloads: int + +@dataclass(frozen=True) +class MetricValue: + name: str # e.g. "ramp_up_time" + value: float # [0,1] + latency_ms: int + +@dataclass(frozen=True) +class ReportRow: + # Required NDJSON fields + per-metric latencies (values are stubs for now) + name: str + category: Category + net_score: float + net_score_latency: int + ramp_up_time: float + ramp_up_time_latency: int + bus_factor: float + bus_factor_latency: int + performance_claims: float + performance_claims_latency: int + license: float + license_latency: int + size_score: Dict[str, float] # {raspberry_pi, jetson_nano, desktop_pc, aws_server} + size_score_latency: int + dataset_and_code_score: float + dataset_and_code_score_latency: int + dataset_quality: float + dataset_quality_latency: int + code_quality: float + code_quality_latency: int + +class SourceHandler(Protocol): + def resolve_revision(self, url: str) -> str: ... + def fetch_meta(self, spec: TargetSpec) -> dict: ... + def stream_files(self, spec: TargetSpec, patterns: list[str]) -> Iterable[tuple[str, bytes]]: ... + +class Cache(Protocol): + def get(self, key: str) -> bytes | None: ... + def set(self, key: str, data: bytes, etag: str | None = None) -> None: ... + +class Metric(Protocol): + name: str + def collect(self, spec: TargetSpec, handler: SourceHandler, cache: Cache) -> Signals: ... + def score(self, signals: Signals) -> MetricValue: ... diff --git a/tests/__pycache__/test_metrics_contract.cpython-313-pytest-8.4.2.pyc b/tests/__pycache__/test_metrics_contract.cpython-313-pytest-8.4.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba1c99c67a5846d80fc96190543bdd1b9ee152ec GIT binary patch literal 3326 zcmdTGU2hvjaPPz4i9?%~^n-wt3Mq%El@B`!Ar7Ra5K>wsAC?M^bh=(Hu8VzVcAY?i zDn*EgmLj!0R75->-de#Q;FUj+kcO5M2nnA0R^cc5#LVs8`D`L09vRuQGqW?hvom`$ z+v@F25`f&*AFjM75b_5yK8POCIs?QVq7qd&PpIII;-bXF3|W+EB!e^J`RHPd##~xD zA74z+1kmycLX&C)aH=9G(MP!8MMZGp#dB}GslHQu7wF!%s)lJX^Mcu=Rm1T1bcZNl z3I46mz%_6{I6=z6d_T#1v_;COV0pYK3T!HlUl#mqIRD>yMMNmX1R^#^p?}=>bbrWP_cGMB-4C9^2IFn~* zmP*W|Y>C>&`zuSYnQN!#&t5pU)HGR>xmc^aw~1+nZ8fQGG_#F$Cox+wE%T!WopUBo zRikF5%9?atyP`AA+t#zp&em*0ud%soU=)Gf*#MwNWRq;C^Y<3M{@~WyjkT@gKlKgY zTlkCN_4?%Gps?|x&cMB9=J}DvdQM@R~Bv1wgzY7mo}Zs|>!0Y_4}8$ZEVn3>=Vx5+bfFO$j8 z6ndCBD4z5gP{RPR3ZPB$b2|Us!frahE$2UvzC+NIAI1?OIYIMbzkm(cl}m1XZwyig=bTu*XZ%%nR_H z$*O3pr3ljYa!?augp{H_x4aD%BB(+|^s5Voi7H)sJtwO2rBkIyPRe^bhOG9jO6@OK zjg*5LQpAhY1zi>IWQRxlSB0Ra_~+1{?y%b5qreI(T;xFiCBNbvs2TA!)o50BD_l&C zsqs>5RB*X?8A=d`&{{`)S$l+f7x&llM_%tNu^?D7{G(9&@+F}N=j{e7p(gCRrR2=F zkna^gGRxn0L}vdCCri1(PzUSCS7y=uCY^0L2WjC);zHxOtS?FN#q=}prz*0nlIUdzmJ-}BH=B#J$r;^&JS*LJW7(E`$^F!shIiH^lGt;aWbfG`Bfp@9_ zw^Q-wf##lDqqw3v&6AsF9;VXU^3azf zUyQ(Tdt@gy1ml+BB48fK?ca_(bbVsW;G+A;glB;U+3n##2I@YyhfDK`U0glT2@?-f zy|)TC3fuDF*1U^$QiBg<@AsoTc)bvCoY(`|cp%$pb2#BC@8W8KPC(q32k9ULM(J_l z{v*V_4$g6nzd*7)8}NL{Qu9MvZNjr75IHeV#_!h=-Kd*Jt(x^-XxYm;yp^!yI?r}b zbw0`5E9<@^++5uyu2(#(@!7h)yjnBoXaQEjuFmi-J(dJP_>GWH$sSRDCDVV(f-u>N J%EGg*?cY|;g#`cr literal 0 HcmV?d00001 diff --git a/tests/__pycache__/test_reporter_schema.cpython-313-pytest-8.4.2.pyc b/tests/__pycache__/test_reporter_schema.cpython-313-pytest-8.4.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2d1c7740ee1ccca076793052a6120b33f8f568f GIT binary patch literal 2003 zcmah}TW=dh6rS;}@0Y}mb8To{he8(`<6NAeVgpqY38l1^vWkS1NUP0ylB`(oZfDj6 z2O;%=2Y5n4s>CyI`~_b50~DwbBP3Mu#M?sNcw%POvv#0@m3_{4zVpqQGZ)Q8v6utw z`|9-D(=QSL{KfW;P39g09q8~bz)<+au0j$uu&d%^jb{>fQ@d%LW>{GOICBCu z^>;q_z6NP_5BVMr_PjGo)Ek;K^9uW4wAn{aTYvr6&xiE4s>TQcp3P)-%jz z1|Z$KC9gFU%)*%I?E)K%~L ze&Y5!0C;ORhnem-034`NwwID<11rCAD>wLGe`u3N($l)afVmqwNTd(;ESdK#_d`D!TV_+Mp0D)q&Nvds3yRrMG2 ztNUsbiYR$dmi>&?sz`3+*~*f#mh^~S!VYv{2l}fgK&#!Ss%!QTRlBI?(TrsV=)}VV zn!NX5d*@x6bx~jt%fskI)}`f`GaM#&tpVnTP7B!V`9|M20-F`Ajz>~Rb4Pt*9Gh0) zVOkmCIA8G*KK5|WbS-39j%oJ@UHCT_>(h>HA(tRJF$8fYN9=PHwN5J|9Oq}cW?&Mw zI@9eMme-ZqtIy}-aT7BCux~ncFrc&lAYx@fRF$&ocqBC%{43GvXz;vwSHL7FgOi>f26WyM(43k0}V9Gsvmece+AqoH*W* z=@{bTWDQ~-BVK`)?uf~4&)vb89q^PPjxsRNhrVOFX23F93NP|5@_d@8*_H8NeLPr; zf~d;Ic)y^GUnUWj_st$sgxQTaMLo0O z4`^NNLX=>eX`Gsb$sYD?jJn3LjhrsQ{Qc%zN8VyD#3S%aZt72v&!>JaPK65hS8oXa ztTG)YS^P|UMfg8bU}io{Glrq&N`x|?IvZv=mjSnyBa{P+*TOvKa-iLbPyv*yVUcqM z@Y2f>DuL=uIKjCRc=c9wHYnw0g*L>_#x~5s5B0KPr5k rn-y+e= 0 diff --git a/tests/test_reporter_schema.py b/tests/test_reporter_schema.py new file mode 100644 index 0000000..ba0dbcd --- /dev/null +++ b/tests/test_reporter_schema.py @@ -0,0 +1,16 @@ +from acmecli.types import ReportRow + +def test_reportrow_has_required_fields(): + row = ReportRow( + name="demo", category="MODEL", + net_score=0.0, net_score_latency=0, + ramp_up_time=0.0, ramp_up_time_latency=0, + bus_factor=0.0, bus_factor_latency=0, + performance_claims=0.0, performance_claims_latency=0, + license=0.0, license_latency=0, + size_score={}, size_score_latency=0, + dataset_and_code_score=0.0, dataset_and_code_score_latency=0, + dataset_quality=0.0, dataset_quality_latency=0, + code_quality=0.0, code_quality_latency=0 + ) + assert row.category == "MODEL"