Scene Metrics Genre additions.
This commit is contained in:
parent
b96e295f53
commit
4989724ae0
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTTW/UMBD9K5HP69R2HCfOrdqCWBXoClaAuE08E2o1dVaOU0DV/nfk7CIoNy6WNfKbeR/jZxbgkVjHro/HV08U0sw2LPm1pIQyXBguqoNUnWg6pUrRVE1jm69sw/wt/WQda7Rz0lniFRniGkXN+2pwHJGsFBKFqnXuCd9m1j0z8CXSk3dUTvMnirOfAuvYZx9w+p5ngy/dOC1YxmmkXZgTBJfJvN9tb/nb6/3hbn9+NdOcwaVH1rG2r6zswXLqXc+1bomDdooPvR5AtAJFk0mAL31IFAOM5YwPf+bjlAIl16lSVaXgyrLThiEkyJR7mOnw85hZrBbd5PpmLa/X7pk9UWSd2vw289zuyo3+6nHuFz/i1flMMD9klcc4HSkmT6snq8xxhOSnUFwmbUdPIbENu/tY7EdIwxQfXxj18ax/9x/yD3n6qx/klkS4nZaQWMeklmzD3oG794GK3Q3rmJCksGqJD0oarlsEDnVluTGVGUg626iM2U4h+bBMy1zsQqJvcRXAOvYaxpnO1K+ju/eJXFpiVvXFZB77OOHiUvHHfylKUVZCZNCSjksqPhD6SC5R1neIy6XfPxBl1AraLjFSSMUe0n3xBuZ71jELrZNgwQIY5waqBQ0CBRjRIBk0hFphg9pqNNoKZzT0UiPUsh9yhW0Y5biLdcN05SoLBnjfC+C6R+J9IxzXUhlraiNq4174WNyNGQeuFZqw4pYG5NpK4FZXFceqhkaZoWnU8Jcll/SlyAYjPZ3/ysrAILaERnKlZc21tZK3tm5437S97U1LtcucbykGGv8y6p13cZqnIRWX5SleWPdhCfm/F+siffeB/1gzupncA8UiZww+5PW+xHo6nU6/AAAA//8DAByDI8o2BAAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTwW7cOAyGX8XQ2XIkWZYt34LJLjpIuxm0QVv0Rot0R6gjD2Q5bRHMuy/kmSLt3vYm/BLJjz+pFxbgiVjPbk+nv54ppIWVLPlNUkIZLgwX9aNUvWh7pSrRmrpT8gsrmb+nn6xnrXZOOku8JkNco2j4UI+OI5KVQqJQjc454evC+hcGvkJ69o6qeflIcfFzYD375APO33Nt8JWb5hWrOE+0D0uC4DLMP/vdPX97e3h8OFxeLbTk4Moj61k31FYOYDkNbuBad8RBO8XHQY8gOoGizRDgKx8SxQBTteC31/o4p0DJ9apSdSW4suxcMoQEGXmAhR5/njLFZtFd1stN3o79C3umyHpV/jLzku7GTf7GzU9PEPBm9MEvR1ayU5xPFJOnzY6tw2mC5OdQXIvsJk8hsZI9fCgOE6Rxjk9/ePTh0vr+f3T+DtzRByr2d6xnQpLCuiM+Kmm47hA4NLXlxtRmJOlsqyQr2W4OyYd1XpdiHxJ9jRsl69nfMC104buN7ugTubTGjP7Z5GL0w6fdjFkQrGSHOOPqUvFqtxSVqGqRLx/WdFpT8Z7QR3KJck+Pcb2m/0+IMmoL2q0xUkjFAdKxeAPLkfXMQuckWLAAxrmRGkGjQAFGtEgGDaFW2KK2Go22whkNg9QIjRzGrGTuPN1iW6hmEOBsp7mzznHdWOTWAXA3CNs2Cgx0GfHV1uJhynHgOqEJa25pRK6tBG51XXOsG2iVGdtWjb9Zcp24FNlvpOfL19gIDGJHaCRXWjZcWyt5Z5uWD2032MF01LjMfE8x0PSbUe+8i/Myj6m4Lkzxh3Xv15C/d7Etz3cf+I9tZHez+0axyCMHH/I2X6d8Pp/P/wIAAP//AwBVXz3zJQQAAA==
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzm6Juht2iCzf1Il60QW8UZxQL0VILito0MPa/F9Rukbi33ISBZvjOM+/MG4n+iKQj96fTL2eMeSE1yeMWEkwYygxl8sBFx9qOs0YwI1vp/iY1GZ/wG+mIVSHw4JBKNEgVME17OQQKgI4zDkxoVWr6zwvp3ogfG8DzGLCZlz8xLeMcSUf+GiPMX8vbfmzCNK/QpHnCXVyyj6GI+WP38ER/v98fnvfXvxZcSnIzAulI0NKjVkAd2J4qDj3tVdtTBlI63jKPwl7TxpgxRT81C3z5/j7MOWIOnWiEbBgVjlxqAj77Irn3Cx6+nYqKDdFjiddbePvs3sgZE+lE/R/Ma7m7MI13y9pPeMbp5NOC6S7Mx6OPQGpySvMJUx5xw3LG1M/LmAvREKxvW86h9VZpC63UQlqQDKFlVoMD540ygwhaD9C3zqpBgeAKDCrVl0Y3cNPk8zjH6qb9YRoxZlKT55dqP/k8zOn4Dv3LlejuJ4AW2aQjkmlprEUlpfTO9IIhWBOMEX3wzA69QB6Qaaes45Zx6QRzUhvbWvDMBVKTDz68jhGr3SPpCOMoQLZIB8ENVS146rV01BhpBuTBWcFJTR7mmMe4zutS7WLGz2nrl3TkVz8teO30PoXXMWPIayoQPplixn2aYQ25+m4AzhrWSMZK0ppPa64+IowJQ8aC45DWW73/pQgjtqSHNSWMudr7/Fr95pdX0hHn28C98857E8KAmuHAgHnDLKABg6AEWFBOgVGOBaN8zxV4zfuhREhNsPit2izOJfYtd5YiV5wq2w60N5ZTjm5AXfYS/TuO1fNU8nxomUKQ1OEAVDnuqVNSUpDaW2EGa8XwA5KbWTgrgAHP12XdFBiAFsFwKhTXVDnHaeu0pb1te9ebFnUomp8wRZx+APVhDGle5iFXN69V79B9XGM5ONXmu69jpP9sM3qcwxdMVZmxH2PZr9tYLzU5ol/WhMftXnVvZF+WqzpsZ0vwRiuha/KSfcrr6RaWtpFOqcvlcvkXAAD//wMAUD50S/QEAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS6Som2G3qOumXsSLtOiN5IxsIVpqQVGbBsb+94DaDRL31psw0DszfOadeSPRHZD05PZ4/OWEMS+kJnncQoIJTZmmTO656FnXc9ZwI5VQ8h9Sk/ERv5KeGBUCDxapRI1UAWupl0OgAGg548BEq0pO97KQ/o24sQE8jQGbefmEaRnnSHry1xhh/lJqu7EJ07xCk+YJH+KSXQylmT8f7h7pH7e7/dPu8teCSxE3I5CehFY6bBVQC8ZTxcFTrzpPGUhpecccCnORjTFjim5qFvj8oz7MOWIOvWiEbBgVlpxrAi670rJ3C+6/HksXG6L7Eq+38PbZv5ETJtKL+jvMS7qbMI03eT5OeMLp6NKC6SbMh4OLQGpyTPMRUx5xw7K9dJpcHudYXYvdTSPGTGry9FztJpeHOR3esXq+IHj4HwROmDzpiWSt1MagklI6q71gCEYHrYUPjpnBC+QBWWuVsdwwLq1gVrbadAYcs4HU5GWavZt+XzZ+VjDfATNCC1RohdLaBoayw6E1yMBp7XUXLMjOCNVBJ511jluPJlgdikE+uPA6Rqwe7klPGEcBskM6CK6p6sBR10pLtZZ6QB6sEZzU5G6OeYzrvC7VQ8z4kjZ+pCe/umnBC7nbFF7HjCGvqUD9W5diuzTDGnL1wwGcNayRjBXRmo9rrj4ijAlDxoJ3n9Zrvv9IhBab6G5NCWOudi6/Vr+55bVQcV3gzpan6hAGbBkODJjTzABq0AhKgAFlFWhlWdDKea7AtdwPJUJqgsVw1eZxLblnMgzUc2sKE6RedoyCYq0QXniv2TuO1dNUdC50TCFIanEAqix31CopKcjWGaEHY8TwE5Kr+TgrgAFPl229dADQIWhOheItVdZy2tnWUG86b73usA2l50dMEaefQH0YQ5qXecjV1bvVO3Qf11guTrX5+MsY6b/bjO7n8BlTVWbsxlgW7DrWc00O6JY14WE7WP0b2ZXtqvbb3RK8aZVoa/KcXcrr8RqWppFWqfP5fP4GAAD//wMAdisCNPUEAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUS2/bOBD+KwLPpkJSFCUK2IPhtFgjTWMk7u5ibxQ5jInIokFRToMi/31BWa6dooe9CaN5fY/hD9SrPaAGLQ+HT0fo44AWKLopxAgTmAhMii1lDakbSvKy4qSS9F+0QO4O3lCDKq411RJwAQIwN6TEbWE1NgYkJdQQVvLUUz0PqPmBlMsNHJ2G3A9/QRic71GD/na98a9ptnK57vxo8uA7WPdDVL1Oy3xdr+7wl+Vm+7A5ZQ0wpOLcGdQgXRYKSm6wNFWLOTUtbnndYmKKQtKaKGDVqcz1EUKvunwwL5f5xsceom5YzoqcYCbR+wIZFVVauVUDbN8OaYuJotsUX0zh6bP5gY4QUMMWZzJP7W505272Qzu6ztxEFZ4h2qD28OrDCxxVhxboEPwBQnQwcfPwlF1WoiQnOROMELRA90rvXA/Z+jZ76BJgpWvCwRRYgjWYS6qw5EWBTVGqiglbVcyiBbqD0EN31fXe6eAHb2M2U559mLPyfXT96MchW/cRnoOKp7rPqhsALdBmbDs37B5BmbetfxzTv37sEpbVGAL0MduouMv+VMMONUiqWlMllVRKaG2hJGCJIUqQyoAwAgxnpjJcciO4JFpw1VJuVElbmyJogR7GeBjjmf/vV0tsg9vvwVw22E4cbzoVrQ/7tYE+OuuSNOeMyVBdN6HK5p6rzkEfP5CMGkQoMFPUgC2jAvPaKKzKQmIhCmGBalkxeiLMuufxJ0/EWsMLW9a1aGVLCTBb1ZxUtiVG20JLU7ZQsYpUNdRSE9KWnNat1MQALblJ6II3o46/WqGYBHoc+3Sd2TrBfnU9/i74hZFN8NFr3yXBXAdPb0OE/e9UW/n9wQ8uJvx2lvbbAPe+T3/n+585m4uXPl5ikO4gm06PSQaFMC0ubCEwL1qLaysFVpyyumZlIblGC3Tr9QuELBlMuX7S5Oyph6dsGfTORdBxDGnyPxOok5yfzydz4SP/+mm78gGWh8Nijv5xTCSdmp0N8OFd+Tak9J0f4hXkGdoTdPa82JWdDBxPT5VLQWFMDUZQzDgtMZeS4lqWFW6rupWtqKHUF7tmj2BcAB2nftswpmkzs7/z5TJEZ5WOQzqeL15Pdpr9Oaf8uuSVbD+rT8OvpJutNHeiJFn26fRwTg76X+/m+/v7+38AAAD//wMAvph9rCgGAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUXW/jKBSG/4rFtXEBY2x81013tFGn02ianV3tHYbjFNWBCHBnqyr/fYSTqumM9mLvrGPe8/GcF16RU3tAPbo+HH5/BpciKlGyS4gRJjARmNRbynrS9ZRUrOas5fQfVCJ7Cy+oRy3XmmoJuAYBmBvS4KEeNTYGJCXUENbwnFPtIupfkbKVgWerofLxG4RovUM9+ss647/n2spWevKzqYKfYO1iUk7nZr6sV7f48/Vme785nYoQs7iyBvVIN7WChhssTTtgTs2AB94NmJi6lrQjClh7klmXIDg1VdE8vdc3PjlIumcVqyuCmUTHEhmVVG55UBG2L4fcxYLoJsfLJbx89q/oGQLqWfkG85TuSk/2ah+H2U7mavK7nXW7lXej3c1BpVy4RIfgDxCShQXOb9ap8PLZ73Y5H/qkpgioRFsIe+vUdPpxA6Oap/Tg57CQiebpv86gHqk5eVSiheQ0LWWL8zCryYJLv2gvCt8/FJtJpdGH/YclPZzYr/8H+julH62DYn2DekQoMFN3gEdGBeadUVg1tcRC1GIEqmXLKCrRyrtk3eznWKxdgt2Z2of+roN+tAl0mkOe6W+RzbYJ3sw6Fe8LpqQiVU1IFs3pMKfiKxgbQCfIQ2zDfM73k4QJtog+2Ql+wbOaQwCXio1Kj8UfKj6iHknVaaqkkkoJrUdoCIzEECVIa0AYAYYz0xouuRFcEi24Gig3qqHDmCOoRJBNViy+HnQzjGJguIaxxVxojgcpNVZCt11HJe2o+AC3uJ+yTumOcDA1ljAazCVVWPK6xqZuVMvE2LZsvOB0NgQlmfqlCf+MYM5e+nKy9tvsBp5P93jpUxjTgREUM04bzKWkuJNNi4e2G+QgOmh0nmzlXfRvIL9BGHy0KT8id9bZvZp+PvK+mVsIDqaL7dxZHXz0YyrOtiw+7Ovr7PIrViwW/W4d/ncxxo3XTxCKbCxl3cUyj8fj8QcAAAD//wMA8IdRlQwFAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxU227cNhD9FYGvFRXeRJF6c20HXaSOjeyiLfo2JEc2a5laUJSTwPC/F9LauaJAXwRhSM45c+bMPJEED0h6cnY8Xj5iKjOpSYlbSDChKdOUyQMXPTM95w23nCvb/U1qEt/hZ9KTTnnPvUUqUSNVgbXUycHTENByxgMTrVpzwu1M+icCsQn4GD020/wH5jlOifTkz5jC9HHFhtj4cVpCk6cRd2kukPxK5v3u/B39/ezmcH1zujXjvD5uYiA98a0EbFWgNnSOKh4cdco4yoKUlhsGKLrTs5gK5gRjM4f7r/hhKgmL70UjZMOosOS5JgEKrJQdzHj4fFxZbBJdrPF6C2+//RN5xEx6Ub+KeUr3xo/xzcPsljiGN9uX1OSYpyPmEnFT43pffSXBWcMaoQVjpCZX4O9iwmp3UV2Pa4ngDVMYJLU4BKosB2qVlDTIFjqhh64TA6nJO8wJx2+yXkWfp3kaSvUicvUdzv7s/DKBG3HFeAvjjKQmu3lfIAUYp4SXn9Av5ZTrkJf1+HxKJaZlWuZqlwreZng5f31/vuSMqVQ3UO6q32C+Iz2xYDwHCxZAez9gy3BggYFmXUAdNAYlQheUVUEry7xW4LgK0HI3rBFSk19XDfeL9zjPX9lsJhnHjUP10qfzMWIq38lIesI4iiAN0kFwTZUJQKGVlmot9YDc207wV5gD5FsspCeDcc4x751QlnsQFmQYmAx6sEJ0HahBtp0SWresAx+6TnAVZIce5CCZEaQmN3kKiy8/tlpuDbjJ0z/oyyoV6YlmbWs8OCe04gasQcW1MtpiZ9UATEnvgLcGnOMMWuatER64tqBcaIcvKl2m25jwIs7HET5/g2sa3chfPIDhA6gfrr/N8IAfp3z//mTj5v3lgdTkw5LWjVDtVot8jIl+0utEXy1jiYe7jBAwXE0Bf/YRruNSnSaUW9SiU7RToqXKDJYacIy2zPFWGtcalKQmF5O/x1ytDoOY1qn6kux6X51lfxcL+rLkld9fG4/rfXUzQhmm/PDdJvmmsB8FaITkbO1MwMfTNto46hAMBs2pULylylpOjW076jrjrNMGW78qdr2U41KqDxhiRl+2il+8uGGe36G//1mMVxe8WJSz1Wv70xbbpP1fS+y5Jg8I85LxYdvW/RPZpYR5Q75YTqO4S1dxHOOMfkphJr3VurGGiReC/3WNMy6ajmn7/Pz8/C8AAAD//wMAfy8DGxoGAAA=
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRUTW/bOhD8KwLPpsIvUaJugdPiGXltjNZoi94o7qoholAGSaUNAv/3grKLNKeHdyGIFWc5OzPUCwn2EUlPro/Hd08YciIbkv1aEkxoyjRl8sBFz7qe85p3bcNa/p1siL/FZ9KTVjnHnUEqUSNVwBo6yNFRADSccWCiUaWn/ZFI/0KsrwGfvMN6Tl8wJj8H0pOvPsD8s9xtfe2meYE6zhPuQso2uELm4257S/+93h/u9udTCVMB1x5IT1wjLTYKqIF2oIrDQAfVDZSBlIZ3zKJozzAfMsZgpzrBw+v9MOeA2fWiFrJmVBhy2hCw2RbKg014eD4WFqtEN6W+Wcvrtn8hTxhJLzZ/xDy3u3KTv3pMw+InuDqv2aaHtK6jdXmOz2RDjnE+YsweV32uU8LHYXo+2PTw/nymbNO7X+iWjLCdl5BJT7gsA60CTZPNfg7VheN28hjy+jFHH5J3/9VLkg25+1ztJ5vHOT6+8ePzWebd/1D5g3X3PmC1uyE9YRwFyA7pKLimqgNLbSMN1VrqEbkzreBkQ7ZzyD4s85KqXcj4I64jkZ68t1PCM7/r6O59RpeXWOb8pkuu9nGGxeXq1UvOalZLxgpoycclV58QfESXsQxxiMul35e3EKHFCtouMWLI1d7m++ofm+5JT4ztHLfGGmu1cyM2DEcGzGrWAmrQCEpAC8oo0Mowp5UduALb8GEsFbIhWKJTrWmV3WAGJQU1IJEqaxvaoRkoDlpjiy1XvBB51bG6mwrOuo4pBEkNjkCV4ZYaJSUF2dhW6LFtxfiXJJc8cFYEBnw6v7uVgQboEDSnQvGGKmM47UzT0qEt1HSHjSucbzEGnP4S6oN3cU7zmKtLQqo30n1aQvl3VGtafvpAf60e3czuAWNVPLY+lKdysfV0Op1+AwAA//8DAIAUdzaCBAAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTwW7cNhD9FWHOokxSFCXqZqxTdOEmXiSLtOiN4oxiwjK1oCgngbH/HlC7QeLeeiGIAd/Me28eXyHYZ4Iebk+ndy8U0gIlJL+VJJeacc14fRSy510vRCU6U0vV/Qsl+Hv6Dj20yjnhDLGaNDGFvGFDPTqGSEZwgVw2Kve0XxboX8H6CunFO6rm5TPFxc8BevjbB5y/5tnWV26aV6ziPNE+LMkGl8l82O/u2V+3h+PD4fJqoSWDK4/Qg2tqS41CZrAdmBI4sEF1A+NY10Z03JJsLzAfEsVgp2rBp1/zcU6BkutlJeuKM2ngXALaZDPlwS50/H7KLDaL7nK93MrbtX+FF4rQy/KnmZd2N27yN8/LsPoJby5nsstTVnmK84li8rR5ssmcJpv8HIrrpN3kKSQo4eFTcZhsGuf4/MaoTxf9+/8h/5inv/tGbk2Eu3kNCXoQikMJ76179IGK/R30wAVJrDtioxSaqQ4ts01tmNa1Hkk400oBJezmkHxY53Up9iHRl7gJgB7+sNNCF+q30T36RC6tMav6R+csHOKMq0vFL/8Fr3hV88zkYU2nNRUfCX0klyjrO8b12u8/EKnlBtqtMVJIxcGmx+JPuzxCD8Z2TlhjjbXauZEaTiNHbjVvkTRqQiWxRWUUamW408oOQqFtxDDmCpRAed3FljDsmsE41zBRY8uUVpYNshFMkhxpGFEM9Vsfi4cp46zruCKsmaERmTLCMqPqmmHd2FbqsW3l+Jsl1+0Lng1Gern8lY2BRuwItWBSiYYpYwTrTNOyoe0GM+iOGpc531MMNP1m1Hvv4rzMYyqu4SneWPdxDfm/F1uQvvrAvm07upvdE8Ui79j6kON9Xev5fD7/AAAA//8DAFPJlSU2BAAA
|
||||
@ -1,5 +0,0 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTy27cOBD8FYFnUeZLlKibMd5FBk7Wg8RIFnuj2K0MEZkcUJSTwJh/X1AzQeLcciOKrO7qquYLCfYJyUBuT6e/njHkhdQk+w0STGjKNGXykYuB9QPnDTe87aX8j9TE3+N3MpBOOcedQSpRI1XAWjrKyVEANJxxYKJVpab9vJDhhVjfAD57h01cPmJafAxkIJ98gPi19La+cXNcoUlxxn1Ysg2uiPlnv7unb28Pjw+Hy6sFl0JuPJCBuFZabBVQA91IFYeRjqofKQMpDe+ZRdFdaD5kTMHOzQJffvaHmANmN4hGyIZRYci5JmCzLZJHu+Dj91NRsVl0V/B6g7fj8EKeMZFB1D/MvJS7cbO/cfHpyQa4mXzwy5HU5JTiCVP2uNmxTTjPNvsYqmuT3ewxZFKThw/VYbZ5iunplUcfLqPv/2Dyd9YdfcBqf0cGwjgKkD3SSXBNVQ+W2lYaqrXUE3JnOsFJTXYxZB/WuC7VPmT8nDaVZCB/23nBi77b5I4+o8trKtL/1SVr/ObzLkIBGKnJIUVYXa5+2s1ZwxrJyuXDmk9rrt4j+IQuY5npMa3X8r9RhBYbabemhCFXB5uP1Ru7HMlAjO0dt8Yaa7VzE7YMJwbMatYBatAISkAHyijQyjCnlR25AtvycSpI0V3SrbaFGpXU3eSAWpAjVc6NdGRtT7FTLWMgrOzGV7ZWD3PhWdczhSCpwQmoMtxSo6SkIFvbCT11nZh+seSaOGfFb8Dny9fYFGiAHkFzKhRvqTKG0960HR27fjSj7rF1RfM9poDzL0a98y7FJU65ui5M9cq692so37valuerD/TbFtlddF8wVSVy60PZ5mvK5/P5/D8AAAD//wMAsOqwDyUEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cRgyG/4owZ4083x+6GXaLLtzUi3jRBr1RM1QsRCstRqNNA2P/ezHaLRL3lptAiJyXD1/yjUxwRNKS+9PplzNOeSE1ycMWEkwYygxl8sBFq1wrZOOMdlyzv0lNhif8RlpiVQg8eKQSDVIVmaad7AONET1nPDKhVakJnxfSvhEYmojnIWAzL39iWoZ5Ii35a5ji/LW8DUMTxnmNTZpH3E1LhikUMX/sHp7o7/f7w/P++teCS0luhkhaEpkHHqShQVpPVe849egUlb3vOEfvQNlr2jBlTBOMzRK/fH8/znnCHFrRCNkwKjy51CRChiK5gwUP305FxYboscTrLbx9tm/kjIm0ov4P5rXcXRiHu2XtRjzjeIK0YLoL8/EIUyQ1OaX5hCkPuGE5Y+rmZciFaAgWnOM8OrBK2+ikFtJGyTA6ZnX00YNRphdB6z52zlvVqyi4igaV6kqjG7hxhDzMU3XT/jAOOGVSk+eXaj9C7ud0fIf+5Up09xNAi2zSEsm0NNaiklKCN51gGK0JxoguALN9J5AHZNor67llXHrBvNTGOhuB+UBq8gHC6zBhtXskLWEcRZQOaS+4ocpFoKClp8ZI0yMP3gpOavIwT3mY1nldqt2U8XPa+iUt+RXGBa+d3qfwOmQMeU0FwidTzLhPc1xDrr4bgLOGNZKxkrTm05qrjxiHhCFjwXFI663e/1KEEVvSw5oSTrnaQ36tfoPllbTEgwscPHgAE0KPmmHPIgPDbEQTDUYloo3Kq2iUZ8Eo6LiKoHnXlwipCRa/VZvFA6hOMY5Uem6pspZRr8rGaYXAhfaC8Xccq+ex5EFwTGGU1GMfqfIcqFdS0ig1WGF6a0X/A5KbWfhWLOL5uqybAhOjw2g4FYprqrzn1HltaWdd5zvjUIei+QnThOMPoD4MIc3L3Ofq5rXqHbqP61QOTrX57usw0X+2GT3O4QumqswYhqns122sl5ocEZY14XG7V+0b2Zflqg7b2bK80U7qmrxkSHk93cKeNU44c7lcLv8CAAD//wMA6GRgTPQEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/jNhCG/4rAs6iQEkWJuqVOFzWy2Rgbd1v0NiJHDhGZNCgq2yDwf19QdhBnFz30Joz4zsczL/lKHOyRdOT6cPj9GV2cSE6iXUIlKyVlkrJqy8tOtF1ZFUq0Vcvqf0hO7C2+kI40QmuuFdIKJVJhWE37atDUGFScccPKWqScsJtI90rAFgafrcbCT98wTNY70pG/rDP+e6oNttCjn00R/IhrN0VwOjXzZb26pZ+vN9v7zenUhFMSF9aQjhimgOtKUl01ioqh5VRhK2g1qJ5zVC2I5iSzLmJwMBaTeXqvb3x0GHVXFmVVMFoqcsyJgQip5R4m3L4cUhcLopsUz5fw8tm9kmcMpCvzN5indFd6tFf7qZ/taK5Gv9tZt1t5N9jdHCCmwjk5BH/AEC0ucH6zDsLLZ7/bpXzkE4wTkpxsMeytg/H04wYHmMf44OewkJnM03+dIR2BOXqSk4XkOC5ls/Mwq9Gii79oLwrfP2SbEeLgw/7Dkh5O7Nf/A/0d6EfrMFvfkI4wjqWpWqRDySUVrQEKdaWolJUckGvVlJzkZOVdtG7285StXcTdmdqH/q6DfrQRdZxDmulvmcy2Cd7MOmbvC+asYEXFWBLN8TDH7CsaG1BHTENsw3zO95OklOUi+mRH/AXPag4BXcw2EB+zP2B6JB1R0GoOChSA1HrAmuHADAPJGoPSSDSiNI0RShgpFNNSQM+FgZr3Q4qQnGAyWXbytSgr1g6GSkBGhdI9batGUl0DSFVr0ff1B7jZ/Zh0oFsm0FRU4WCoUByoElVFTVVDU8qhacrhgtPZEJwl6pcm/HNCc/bSl5O132Y3+Hy6x0uf0pgWjeS0FLymQilOW1U3tG/aXvWyxVqnyVbeTf4N5DcMvZ9sTI/InXV2D+PPR943c4vB4XixnTurg5/8ELOzLbMP+/o6u/SKZYtFv1tH/12MceP1E4YsGQusu1jm8Xg8/gAAAP//AwBBBoIlDAUAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzS/zQzbBb1HVTL+JFWvQ2Ike2EC21oKhNA2P/e0DtBol7600Y6J0ZPvPOvJEIByQduT0efzlhzAupSR63kGBCU6Ypk3suOmU7IRsrmNOG/UNqMj7iV9IRo7zn3iGVqJGqwFray8HTENBxxgMTrSo54WUh3RuBsQl4Gj028/IJ0zLOkXTkrzGG+UupDWPjp3kNTZonfIhLhuhLM38+3D3SP253+6fd5a8FlyJuxkA6EpgD7qWmXhpH1WA5dWgVlYPrOUdnQZmLbIwZU4SpWcLnH/XDnCNm34lGyIZR4ci5JgEylJZ7WHD/9Vi62BDdl3i9hbfP7o2cMJFO1N9hXtLd+Gm8yfNxwhNOR0gLphs/Hw4QA6nJMc1HTHnEDcv20mmCPM6xuha7m0aMmdTk6bnaTZCHOR3esXq+IHj4HwROmHrSEclaqY1BJaUEp3vBMBjttRa9B2aGXiD3yFqnjOOGcekEc7LVxpoAzHlSk5dp7mH6fdn4OcF6G5gRWqBCJ5TWzjOUFofWIAugda+td0FaI5QNVoID4K5H4532xSAfwL+OEauHe9IRxlEEaZEOgmuqbAAKrXRUa6kH5N4ZwUlN7uaYx7jO61I9xIwvaeNHOvIrTAteyN0m/zpm9HlNBerfuhTbpTmsPlc/HMBZwxrJWBGt+bjm6iOGMaHPWPDu03rN9x+J0GIT3a0pYczVDvJr9Rssr4UKWM/Bladq7wdsGQ4sMNDMBNRBY1AimKCcClo55rWCnqsALe+HEiE1wWK4avO4RaZbDo4aN3CqbAvUDU5TztC5wUFv2PCOY/U0FR14yxQGSR0OgSrHgTolJQ2yBSP0YIwouu9IrubjrAAOeLps69aBDsFi0JwKxVuqnOPUutbQ3tje9dpi60vPj5giTj+B+jD6NC/zkKurd6t36D6usVycavPxlzHSf7cZ3c/+M6aqzBjGWBbsOtZzTQ4Iy5rwsB2s7o3synZV++1uGd60VrY1ec6Q8nq8hh1rrLD6fD6fvwEAAP//AwBMsVlF9QQAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUS2/bOBD+KwLPokJS1IMC9mA4LWqkaYzE3V30RpHDmIgsGhTlNAj83xeU5dopetibMJrX9xi+o17uADVosd9/OkAfBpSiYKcQI6zEpMQk31DW8LphPMsFKwpS/0ApsnfwhhpUcaWoEoBzKAFzTQrc5kZhrUFQQjVhBY895fOAmnckbabhYBVkbvgb/GBdjxr0j+21e42zpc1U50adedfBqh+C7FVc5ttqeYe/Ltabh/Upa4AhFmdWowZpIiRVeYlVXgnMTU2xgJrj3IiWUhC15NWpzPYBfC+7bNAvl/nahR6CaljG8oxgJtAxRVoGGVdu5QCbt33cYqLoNsbTKTx9Nu/oAB41LD2TeWp3ozp7sxva0Xb6Jkj/DMF4uYNX51/gIDuUor13e/DBwsTNw1NyWYmSjGSsZISgFN1LtbU9JKvb5KGLgKWqCQedYwFGYy6oxILnOdZ5IStWmqpiBqXoDnwP3VXXe6u8G5wJyUx58mHO0vXB9qMbh2TVB3j2MpzqPstuAJSi9dh2dtg+gtRvG/c4xn/92EUsy9F76EOylmGbfJHDFjVIyFpRKaSQslTKQEHAEE1kSSoNpS5Bc6YrzQXXJRdElVy2lGtZ0NbECErRwxj2Yzjz//NqiY23ux3oywabieN1J4NxfrfS0AdrbJTmnDEZqusmVMncc9lZ6MMHklGDCAWm8xqwYbTEvNYSyyIXuCzz0gBVomL0RJixz+MvnogxmuemqOuyFS0lwExVc1KZlmhlciV00ULFKlLVUAtFSFtwWrdCEQ204Dqi806PKvxuhXwS6HHs43Umqwj71fb4Z8kvjKy9C065LgpmO3h6GwLs/qTa0u32brAh4jeztN8HuHd9/Dvf/8zZXLxw4RKDeAfJdHoF10CNYbjSUGFeCIlFbgzmpCCtqVql8gKl6NapF/BJNJi0/aTJ2VMPT8nCq60NoMLo4+R/J1AnOT+fT+bCR/bt02bpPCz2+3SO/nWIJJ2anQ3w4V35PsT0rRvCFeQZ2hN05rzYlZ00HE5PlY3BUusadEkx47TAXAiKa1FUuK3qVrRlDYW62DV5BG09qDD12/gxTpuZ/ZMvFz5YI1UY4vF8dWqy0+zPOeX3Ja9k+1V9Gn4l3WyluRMl0bJPp4dzctD/ejePx+PxPwAAAP//AwBie1/EKAYAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxU227cNhD9FYGvFRXexIveXNtBF6ljI7toi76NyJHNWqYWFOUkMPzvhbR2rijQF0EYknPOnDkzTyTBA5KOnB2Pl4+YykxqUuIWEkxoyjRl8sBFp2wnRaO44UrIv0lN4jv8TDpilPfcO6QSNVIVWEt7OXgaAjrOeGCiVWtOuJ1J90QgNgEfo8dmmv/APMcpkY78GVOYPq7YEBs/Tkto8jTiLs0Fkl/JvN+dv6O/n90crm9Ot2ac18dNDKQjgTngXmrqpXFUDZZTh1ZRObiec3QWlDk9i6lgTjA2c7j/ih+mkrD4TjRCNowKR55rEqDASrmHGQ+fjyuLTaKLNV5v4e23eyKPmEkn6lcxT+ne+DG+eZj7JY7hzfYlNTnm6Yi5RNzUuN5XX0lw1rBGaMEYqckV+LuYsNpdVNfjWiJ4yxQGSR0OgSrHgTolJQ2yBSP0YIwYSE3eYU44fpP1Kvo8zdNQqheRq+9w9mfnlwn6EVeMtzDOSGqym/cFUoBxSnj5Cf1STrkOeVmPz6dUYlqmZa52qeBthpfz1/fnS86YSnUD5a76DeY70hEH1nNw4AC09wO2DAcWGGhmAuqgMSgRTFBOBa0c81pBz1WAlvfDGiE1+XXVcL94j/P8lc1mknHcOFQvfTofI6bynYykI4yjCNIiHQTXVNkAFFrpqNZSD8i9M4K/whwg32IhHRls3/fM+14oxz0IBzIMTAY9OCGMATXI1iihdcsM+GCM4CpIgx7kIJkVpCY3eQqLLz+2Wm4NuMnTP+jLKhXpiGZtaz30vdCKW3AWFdfKaofGqQGYkr4H3lroe86gZd5Z4YFrB6oP7fBFpct0GxNexPk4wudvcG2jG/mLB7B8APXD9bcZHvDjlO/fn2zcvL88kJp8WNK6EardapGPMdFPep3oq2Us8XCXEQKGqyngzz7CdVyqbUINZ9JohVQMXFDVBkYhDJJaz53XUljhJanJxeTvMVerwyCmdaq+JLveV2fZ38WCvix55ffXxuN6X92MUIYpP3y3Sb4p7EcBGiE5WzsT8PG0jTaOOgSLQXMqFG+pco5T61pDe2N712uLrV8Vu17KcSnVBwwxoy9bxS9e3DDP79Df/yzGqwteLMrZ6rX9aYtt0v6vJfZckweEecn4sG3r7onsUsK8IV8sp1Hcpas4jnFGP6Uwk84qyxvGjHph+J/3WiMbo5h8fn5+/hcAAP//AwD/LioFGwYAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTwW7cOAyGX8Xg2XIkWZYt34JJFzvIthm0g+5ib7JIN0IceSDLaYtg3r2QZ4o2e9uLIBAi+f8fqVcI9pmgh9vT6d0LhbRACclvIcmlZlwzXh+F7FXX17JSouG1rv+FEvw9fYceWuWccIZYTZqYQt6woR4dQyQjuEAuG5Vr2i8L9K9gfYX04h1V8/KZ4uLnAD387QPOX3Nv6ys3zStWcZ5oH5Zkg8tiPux39+yv28Px4XB5tdCSkyuP0ANyY4WrNXN1a5gaO8EMdYrVoxmEINNZ1V7SfEgUg52qBZ9+9cc5BUqul5WsK86kgXMJaJPNkge70PH7KavYEN3leLmFt2v/Ci8UoZflT5iXcjdu8jfPy7D6CW8uZ7LLU3Z5ivOJYvK0MdlsTpNNfg7FtdNu8hQSlPDwqThMNo1zfH4D6tPF//5/2D/m7u++kVsT4W5eQ4IehOJQwnvrHn2gYn8HPXBBEuuO2CiFZqpDy2xTG6Z1rUcSzrRSQAm7OSQf1nldin1I9CVuBqCHP+y00EX6bXSPPpFLa8yu/tF5Fw5xxtWl4hd/wSte1TwreVjTaU3FR0IfySXK/o5xvdb7T4rUckvarTFSSMXBpsfiT7s8Qg/Gdk5YY4212rmRGk4jR241b5E0akIlsUVlFGpluNPKDkKhbcQw5giUQHncxbZh1ikp7YAMeWOZQt1kxCPTyFHhiHrg+IZj8TBd8jquCGtmaESmjLDMqLpmWDe2lXpsWzn+huQ6fcEzYKSXy1/ZFGjEjlALJpVomDJGsM40LRvabjCD7qhxWfM9xUDTb6DeexfnZR5TcV2e4g26j2vI/73YFumrD+zbNqO72T1RLPKMrQ95va9jPZ/P5x8AAAD//wMAJohFAjYEAAA=
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRUTW/bOhD8KwLPpsIvUaJugdPiGXltjNZoi95W5KoholAGSaUNAv/3grKLNKeHdyGIFWc5OzPUCwnwiKQn18fjuycMOZENyX4tCSY0ZZoyeeCiV10vRa245Kxrv5MN8bf4THrSKmu5NUglaqTKsYYOcrTUOTScccdEo0pP+JFI/0LA1w6fvMV6Tl8wJj8H0pOvPrj5Z7kbfG2neXF1nCfchZQh2ELm4257S/+93h/u9udTCVMB196RnjhmgFupqZWtoWrsODXYKSpHM3COpgPVnmE+ZIwBpjq5h9f73ZwDZtuLWsiaUWHIaUMcZCiUB0h4eD4WFqtEN6W+Wcvrtn8hTxhJLzZ/xDy3u7KTv3pMw+Ind3VeM6SHtK4j2DzHZ7IhxzgfMWaPqz7XKeHjMD0fID28P58p2/TuF9olo9vOS8ikJ1yWgVaBpgmyn0N14bidPIa8fszRh+Ttf/WSZEPuPlf7CfI4x8c3fnw+y7z7Hyp/AHvvA1a7G9ITxlE42SEdBddUdQ4oNNJQraUekVvTCk42ZDuH7MMyL6nahYw/4joS6cl7mBKe+V1He+8z2rzEMuc3XXK1j7NbbK5eveSsZrVkrICWfFxy9Qmdj2gzliEOcbn0+/IWIrRYQdslRgy52kO+r/6BdE96YqCzHAwYAG3tiA3DkTkGmrUOtdPolHCtU0Y5rQyzWsHAlYOGD2OpkA3BEp1qTasZO902wCh0CFShUXSQTlAOgrFWO2XbQuRVx+puKjiwHVPoJDU4OqoMB2qUlNTJBlqhx7YV41+SXPLAWRHY4dP53a0MtHMdOs2pULyhyhhOO9O0dGi7wQy6w8YWzrcYA05/CfXB2zineczVJSHVG+k+LaH8O6o1LT99oL9Wj25m+4CxKh6DD+WpXGw9nU6n3wAAAP//AwCCZsjSggQAAA==
|
||||
@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTy27cOBD8FYFnUeZLlKibMd5FBk7Wg8RIgtwoditDRCYHFOUkMObfF9RM4GRveyOKrO7qquYLCfYJyUBuT6e/njHkhdQk+w0STGjKNGXykYtB9YMUjeJGil5/ITXx9/iTDKRTznFnkErUSBWwlo5ychQADWccmGhVqWm/LmR4IdY3gM/eYROXj5gWHwMZyCcfIH4vva1v3BxXaFKccR+WbIMrYv7Z7+7p29vD48Ph8mrBpZAbD2QgwIzlTmrqZGeomnpODfaKysmMnKPpreouNB8ypmDnZoFvr/0h5oDZDaIRsmFUGHKuCdhsi+TRLvj481RUbBbdFbze4O04vJBnTGQQ9S8zL+Vu3OxvXHx6sgFuJh/8ciQ1OaV4wpQ9bnZsE86zzT6G6tpkN3sMmdTk4UN1mG2eYnr6w6MPl9H3/2Pyd9YdfcBqf0cGwjgKkD3SSXBNVQ+W2lYaqrXUE3JnOsFJTXYxZB/WuC7VPmT8mjaVZCB/23nBi77b5I4+o8trKtI/65I1/vB5F6EAjNTkkCKsLlevdnPWsEaycvmw5tOaq/cIPqHLWGZ6TOu1/H8oQouNtFtTwpCrg83H6o1djmQgxvaOW2ONtdq5CVuGEwNmNesANWgEJaADZRRoZZjTyo5cgW35OBWk6C7pVttCcaNGPnWWguBA1TiOtEcOFLvWKKXZ1Mki8dXW6mEuPOt6phAkNTgBVYZbapSUFGRrO6GnrhPTb5ZcE+es+A34fPkamwIN0CNoToXiLVXGcNqbtqNj149m1D22rmi+xxRw/s2od96luMQpV9eFqf6w7v0ayveutuX57gP9sUV2F903TFWJ3PpQtvma8vl8Pv8LAAD//wMA/b2d/iUEAAA=
|
||||
Binary file not shown.
@ -14,7 +14,7 @@ public sealed class ProjectsController(IProjectService projects) : Controller
|
||||
return model is null ? NotFound() : View(model);
|
||||
}
|
||||
|
||||
public IActionResult Create() => View("Edit", new ProjectEditViewModel());
|
||||
public async Task<IActionResult> Create() => View("Edit", await projects.GetCreateAsync());
|
||||
|
||||
public async Task<IActionResult> Edit(int id)
|
||||
{
|
||||
@ -28,6 +28,11 @@ public sealed class ProjectsController(IProjectService projects) : Controller
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
if (model.ProjectID == 0)
|
||||
{
|
||||
await projects.PopulateGenrePresetOptionsAsync(model);
|
||||
}
|
||||
|
||||
return View("Edit", model);
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,9 @@ public interface IProjectRepository
|
||||
{
|
||||
Task<IReadOnlyList<Project>> ListAsync();
|
||||
Task<Project?> GetAsync(int projectId);
|
||||
Task<IReadOnlyList<GenreMetricPreset>> ListGenreMetricPresetsAsync();
|
||||
Task<int> SaveAsync(Project project);
|
||||
Task<int> SaveAsync(Project project, string? genreMetricPresetKey);
|
||||
Task ArchiveAsync(int projectId);
|
||||
}
|
||||
|
||||
@ -1135,12 +1137,21 @@ public sealed class ProjectRepository(ISqlConnectionFactory connectionFactory) :
|
||||
return await connection.QuerySingleOrDefaultAsync<Project>("dbo.Project_Get", new { ProjectID = projectId }, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task<int> SaveAsync(Project project)
|
||||
public async Task<IReadOnlyList<GenreMetricPreset>> ListGenreMetricPresetsAsync()
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
var rows = await connection.QueryAsync<GenreMetricPreset>("dbo.GenreMetricPreset_List", commandType: CommandType.StoredProcedure);
|
||||
return rows.ToList();
|
||||
}
|
||||
|
||||
public Task<int> SaveAsync(Project project) => SaveAsync(project, null);
|
||||
|
||||
public async Task<int> SaveAsync(Project project, string? genreMetricPresetKey)
|
||||
{
|
||||
using var connection = connectionFactory.CreateConnection();
|
||||
return await connection.QuerySingleAsync<int>(
|
||||
"dbo.Project_Save",
|
||||
new { project.ProjectID, project.ProjectName, project.Description },
|
||||
new { project.ProjectID, project.ProjectName, project.Description, GenreMetricPresetKey = genreMetricPresetKey },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,16 @@ public sealed class Project
|
||||
public bool IsArchived { get; set; }
|
||||
}
|
||||
|
||||
public sealed class GenreMetricPreset
|
||||
{
|
||||
public int GenreMetricPresetID { get; set; }
|
||||
public string PresetKey { get; set; } = string.Empty;
|
||||
public string PresetName { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
|
||||
public sealed class Book
|
||||
{
|
||||
public int BookID { get; set; }
|
||||
|
||||
@ -12,7 +12,9 @@ public interface IProjectService
|
||||
{
|
||||
Task<IReadOnlyList<Project>> ListAsync();
|
||||
Task<ProjectDetailViewModel?> GetDetailAsync(int projectId);
|
||||
Task<ProjectEditViewModel> GetCreateAsync();
|
||||
Task<ProjectEditViewModel?> GetEditAsync(int projectId);
|
||||
Task PopulateGenrePresetOptionsAsync(ProjectEditViewModel model);
|
||||
Task<int> SaveAsync(ProjectEditViewModel model);
|
||||
Task ArchiveAsync(int projectId);
|
||||
}
|
||||
@ -238,6 +240,13 @@ public sealed class ProjectService(IProjectRepository projects, IBookRepository
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<ProjectEditViewModel> GetCreateAsync()
|
||||
{
|
||||
var model = new ProjectEditViewModel();
|
||||
await PopulateGenrePresetOptionsAsync(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
public async Task<ProjectEditViewModel?> GetEditAsync(int projectId)
|
||||
{
|
||||
var project = await projects.GetAsync(projectId);
|
||||
@ -249,12 +258,20 @@ public sealed class ProjectService(IProjectRepository projects, IBookRepository
|
||||
};
|
||||
}
|
||||
|
||||
public async Task PopulateGenrePresetOptionsAsync(ProjectEditViewModel model)
|
||||
{
|
||||
var presets = await projects.ListGenreMetricPresetsAsync();
|
||||
model.GenreMetricPresetOptions = presets
|
||||
.Select(x => new SelectListItem(x.PresetName, x.PresetKey, string.Equals(x.PresetKey, model.GenreMetricPresetKey, StringComparison.OrdinalIgnoreCase)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public Task<int> SaveAsync(ProjectEditViewModel model) => projects.SaveAsync(new Project
|
||||
{
|
||||
ProjectID = model.ProjectID,
|
||||
ProjectName = model.ProjectName,
|
||||
Description = model.Description
|
||||
});
|
||||
}, model.ProjectID == 0 ? model.GenreMetricPresetKey : null);
|
||||
|
||||
public Task ArchiveAsync(int projectId) => projects.ArchiveAsync(projectId);
|
||||
}
|
||||
|
||||
669
PlotLine/Sql/028_GenreMetricPresets.sql
Normal file
669
PlotLine/Sql/028_GenreMetricPresets.sql
Normal file
@ -0,0 +1,669 @@
|
||||
SET ANSI_NULLS ON;
|
||||
GO
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'dbo.GenreMetricPresets', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.GenreMetricPresets
|
||||
(
|
||||
GenreMetricPresetID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_GenreMetricPresets PRIMARY KEY,
|
||||
PresetKey nvarchar(50) NOT NULL,
|
||||
PresetName nvarchar(100) NOT NULL,
|
||||
Description nvarchar(500) NULL,
|
||||
SortOrder int NOT NULL,
|
||||
IsActive bit NOT NULL CONSTRAINT DF_GenreMetricPresets_IsActive DEFAULT 1,
|
||||
CONSTRAINT UQ_GenreMetricPresets_PresetKey UNIQUE(PresetKey)
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'dbo.GenreMetricPresetMetrics', N'U') IS NULL
|
||||
BEGIN
|
||||
CREATE TABLE dbo.GenreMetricPresetMetrics
|
||||
(
|
||||
GenreMetricPresetID int NOT NULL,
|
||||
MetricTypeID int NOT NULL,
|
||||
SortOrder int NOT NULL,
|
||||
CONSTRAINT PK_GenreMetricPresetMetrics PRIMARY KEY(GenreMetricPresetID, MetricTypeID),
|
||||
CONSTRAINT FK_GenreMetricPresetMetrics_Presets FOREIGN KEY(GenreMetricPresetID) REFERENCES dbo.GenreMetricPresets(GenreMetricPresetID),
|
||||
CONSTRAINT FK_GenreMetricPresetMetrics_MetricTypes FOREIGN KEY(MetricTypeID) REFERENCES dbo.SceneMetricTypes(MetricTypeID)
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
MERGE dbo.GenreMetricPresets AS target
|
||||
USING (VALUES
|
||||
(N'Custom', N'Custom / No preset', N'Use the core PlotLine metrics only.', 10),
|
||||
(N'CrimeThriller', N'Crime / Thriller', N'Suspense, danger, investigation and clues.', 20),
|
||||
(N'Romance', N'Romance', N'Emotional intimacy, conflict, trust, longing and chemistry.', 30),
|
||||
(N'Fantasy', N'Fantasy', N'Wonder, world building, magic, politics and quest progress.', 40),
|
||||
(N'Horror', N'Horror', N'Dread, fear, isolation, gore and helplessness.', 50),
|
||||
(N'HistoricalFiction', N'Historical Fiction', N'Immersion, authenticity and social or political pressure.', 60),
|
||||
(N'LiteraryFiction', N'Literary Fiction', N'Character growth, internal conflict, subtext and theme.', 70)
|
||||
) AS source (PresetKey, PresetName, Description, SortOrder)
|
||||
ON target.PresetKey = source.PresetKey
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET PresetName = source.PresetName,
|
||||
Description = source.Description,
|
||||
SortOrder = source.SortOrder,
|
||||
IsActive = 1
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (PresetKey, PresetName, Description, SortOrder, IsActive)
|
||||
VALUES (source.PresetKey, source.PresetName, source.Description, source.SortOrder, 1);
|
||||
GO
|
||||
|
||||
MERGE dbo.SceneMetricTypes AS target
|
||||
USING (VALUES
|
||||
(N'Suspense', N'Rising uncertainty and anticipation of danger.', 5, 210),
|
||||
(N'Danger', N'Immediate physical, emotional, or strategic risk.', 5, 220),
|
||||
(N'Investigation Progress', N'How far the truth-finding or case-solving thread advances.', 5, 230),
|
||||
(N'Clue Density', N'How much meaningful clue material appears in the scene.', 4, 240),
|
||||
(N'Threat Proximity', N'How close the active threat feels to the characters.', 5, 250),
|
||||
(N'Emotional Intimacy', N'Emotional closeness, vulnerability, and connection.', 5, 310),
|
||||
(N'Relationship Conflict', N'Tension, friction, or opposing needs within the relationship.', 5, 320),
|
||||
(N'Trust', N'How much trust is present, earned, tested, or repaired.', 5, 330),
|
||||
(N'Longing', N'Yearning, desire, separation, or unresolved emotional pull.', 5, 340),
|
||||
(N'Chemistry', N'Spark, attraction, and interpersonal charge.', 5, 350),
|
||||
(N'Wonder', N'Sense of awe, strangeness, discovery, or enchantment.', 5, 410),
|
||||
(N'World Building', N'How much setting, culture, history, or rules are developed.', 4, 420),
|
||||
(N'Magic Presence', N'How strongly magic or the fantastic shapes the scene.', 4, 430),
|
||||
(N'Political Intrigue', N'Power manoeuvring, faction pressure, and strategic schemes.', 5, 440),
|
||||
(N'Quest Progress', N'How much the central journey or mission advances.', 5, 450),
|
||||
(N'Dread', N'Slow-building unease and expectation of something terrible.', 5, 510),
|
||||
(N'Fear', N'Immediate fear or panic experienced by characters or reader.', 5, 520),
|
||||
(N'Isolation', N'How alone, cut off, or unsupported characters feel.', 5, 530),
|
||||
(N'Gore', N'Graphic bodily harm or visceral horror content.', 1, 540),
|
||||
(N'Helplessness', N'Loss of control, trapped choices, or inability to act.', 5, 550),
|
||||
(N'Historical Immersion', N'How strongly the scene evokes its period and setting.', 5, 610),
|
||||
(N'Social Pressure', N'Expectations, class, family, reputation, and public constraint.', 5, 620),
|
||||
(N'Political Tension', N'Period power pressure, public conflict, or institutional risk.', 5, 630),
|
||||
(N'Authenticity', N'How heavily the scene relies on period-specific detail or accuracy.', 5, 640),
|
||||
(N'Cultural Conflict', N'Conflict arising from customs, values, identity, or social change.', 5, 650),
|
||||
(N'Character Growth', N'Visible internal change, self-knowledge, or development.', 5, 710),
|
||||
(N'Internal Conflict', N'Inner contradiction, dilemma, doubt, or self-opposition.', 5, 720),
|
||||
(N'Subtext', N'Unspoken meaning, implication, and tension beneath dialogue or action.', 5, 730),
|
||||
(N'Theme Strength', N'How strongly the scene expresses or complicates the work''s themes.', 5, 740),
|
||||
(N'Emotional Resonance', N'Aftertaste, depth, and lingering emotional impact.', 5, 750)
|
||||
) AS source (MetricName, Description, DefaultValue, SortOrder)
|
||||
ON target.ProjectID IS NULL AND target.MetricName = source.MetricName
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET Description = source.Description,
|
||||
MinValue = 1,
|
||||
MaxValue = 10,
|
||||
DefaultValue = source.DefaultValue,
|
||||
SortOrder = source.SortOrder,
|
||||
IsActive = 1
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (ProjectID, MetricName, Description, MinValue, MaxValue, DefaultValue, SortOrder, IsActive)
|
||||
VALUES (NULL, source.MetricName, source.Description, 1, 10, source.DefaultValue, source.SortOrder, 1);
|
||||
GO
|
||||
|
||||
DELETE gpmm
|
||||
FROM dbo.GenreMetricPresetMetrics gpmm
|
||||
INNER JOIN dbo.GenreMetricPresets gmp ON gmp.GenreMetricPresetID = gpmm.GenreMetricPresetID;
|
||||
GO
|
||||
|
||||
INSERT dbo.GenreMetricPresetMetrics (GenreMetricPresetID, MetricTypeID, SortOrder)
|
||||
SELECT gmp.GenreMetricPresetID, smt.MetricTypeID, rows.SortOrder
|
||||
FROM
|
||||
(
|
||||
VALUES
|
||||
(N'CrimeThriller', N'Suspense', 210),
|
||||
(N'CrimeThriller', N'Danger', 220),
|
||||
(N'CrimeThriller', N'Investigation Progress', 230),
|
||||
(N'CrimeThriller', N'Clue Density', 240),
|
||||
(N'CrimeThriller', N'Threat Proximity', 250),
|
||||
(N'Romance', N'Emotional Intimacy', 310),
|
||||
(N'Romance', N'Relationship Conflict', 320),
|
||||
(N'Romance', N'Trust', 330),
|
||||
(N'Romance', N'Longing', 340),
|
||||
(N'Romance', N'Chemistry', 350),
|
||||
(N'Fantasy', N'Wonder', 410),
|
||||
(N'Fantasy', N'World Building', 420),
|
||||
(N'Fantasy', N'Magic Presence', 430),
|
||||
(N'Fantasy', N'Political Intrigue', 440),
|
||||
(N'Fantasy', N'Quest Progress', 450),
|
||||
(N'Horror', N'Dread', 510),
|
||||
(N'Horror', N'Fear', 520),
|
||||
(N'Horror', N'Isolation', 530),
|
||||
(N'Horror', N'Gore', 540),
|
||||
(N'Horror', N'Helplessness', 550),
|
||||
(N'HistoricalFiction', N'Historical Immersion', 610),
|
||||
(N'HistoricalFiction', N'Social Pressure', 620),
|
||||
(N'HistoricalFiction', N'Political Tension', 630),
|
||||
(N'HistoricalFiction', N'Authenticity', 640),
|
||||
(N'HistoricalFiction', N'Cultural Conflict', 650),
|
||||
(N'LiteraryFiction', N'Character Growth', 710),
|
||||
(N'LiteraryFiction', N'Internal Conflict', 720),
|
||||
(N'LiteraryFiction', N'Subtext', 730),
|
||||
(N'LiteraryFiction', N'Theme Strength', 740),
|
||||
(N'LiteraryFiction', N'Emotional Resonance', 750)
|
||||
) rows (PresetKey, MetricName, SortOrder)
|
||||
INNER JOIN dbo.GenreMetricPresets gmp ON gmp.PresetKey = rows.PresetKey
|
||||
INNER JOIN dbo.SceneMetricTypes smt ON smt.ProjectID IS NULL AND smt.MetricName = rows.MetricName
|
||||
WHERE NOT EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM dbo.GenreMetricPresetMetrics existing
|
||||
WHERE existing.GenreMetricPresetID = gmp.GenreMetricPresetID
|
||||
AND existing.MetricTypeID = smt.MetricTypeID
|
||||
);
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.GenreMetricPreset_List
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
SELECT GenreMetricPresetID, PresetKey, PresetName, Description, SortOrder, IsActive
|
||||
FROM dbo.GenreMetricPresets
|
||||
WHERE IsActive = 1
|
||||
ORDER BY SortOrder, PresetName;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.ProjectMetricPreset_Apply
|
||||
@ProjectID int,
|
||||
@GenreMetricPresetKey nvarchar(50) = N'Custom'
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM dbo.Projects WHERE ProjectID = @ProjectID AND IsArchived = 0)
|
||||
RETURN;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM dbo.ProjectTimelineSettings WHERE ProjectID = @ProjectID)
|
||||
BEGIN
|
||||
INSERT dbo.ProjectTimelineSettings
|
||||
(ProjectID, ShowSceneCards, ShowMetricShape, ShowPlotLines, ShowStoryAssets, ShowCharacterAppearances, ShowWarnings)
|
||||
VALUES
|
||||
(@ProjectID, 1, 1, 1, 1, 1, 1);
|
||||
END;
|
||||
|
||||
DECLARE @PresetID int =
|
||||
(
|
||||
SELECT GenreMetricPresetID
|
||||
FROM dbo.GenreMetricPresets
|
||||
WHERE PresetKey = COALESCE(NULLIF(LTRIM(RTRIM(@GenreMetricPresetKey)), N''), N'Custom')
|
||||
AND IsActive = 1
|
||||
);
|
||||
|
||||
DELETE FROM dbo.ProjectTimelineMetricSettings
|
||||
WHERE ProjectID = @ProjectID;
|
||||
|
||||
INSERT dbo.ProjectTimelineMetricSettings (ProjectID, MetricTypeID, SortOrder)
|
||||
SELECT @ProjectID, mt.MetricTypeID, mt.SortOrder
|
||||
FROM dbo.SceneMetricTypes mt
|
||||
WHERE mt.ProjectID IS NULL
|
||||
AND mt.IsActive = 1
|
||||
AND mt.MetricName IN
|
||||
(
|
||||
N'Overall Intensity', N'Tension', N'Emotional Weight', N'Action', N'Darkness',
|
||||
N'Romance', N'Sexual Charge', N'Violence', N'Hope / Lightness', N'Comedy', N'Mystery'
|
||||
)
|
||||
UNION
|
||||
SELECT @ProjectID, mt.MetricTypeID, gpmm.SortOrder
|
||||
FROM dbo.GenreMetricPresetMetrics gpmm
|
||||
INNER JOIN dbo.SceneMetricTypes mt ON mt.MetricTypeID = gpmm.MetricTypeID
|
||||
WHERE gpmm.GenreMetricPresetID = @PresetID
|
||||
AND mt.IsActive = 1
|
||||
AND (mt.ProjectID IS NULL OR mt.ProjectID = @ProjectID);
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Project_Save
|
||||
@ProjectID int = NULL,
|
||||
@ProjectName nvarchar(200),
|
||||
@Description nvarchar(max) = NULL,
|
||||
@GenreMetricPresetKey nvarchar(50) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
IF @ProjectID IS NULL OR @ProjectID = 0
|
||||
BEGIN
|
||||
INSERT dbo.Projects (ProjectName, Description)
|
||||
VALUES (@ProjectName, @Description);
|
||||
|
||||
SET @ProjectID = CAST(SCOPE_IDENTITY() AS int);
|
||||
EXEC dbo.ProjectMetricPreset_Apply @ProjectID = @ProjectID, @GenreMetricPresetKey = @GenreMetricPresetKey;
|
||||
|
||||
SELECT @ProjectID AS ProjectID;
|
||||
RETURN;
|
||||
END;
|
||||
|
||||
UPDATE dbo.Projects
|
||||
SET ProjectName = @ProjectName,
|
||||
Description = @Description,
|
||||
UpdatedDate = SYSUTCDATETIME()
|
||||
WHERE ProjectID = @ProjectID;
|
||||
|
||||
SELECT @ProjectID AS ProjectID;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.SceneMetricType_List
|
||||
@ProjectID int = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @HasMetricSettings bit =
|
||||
CASE WHEN @ProjectID IS NOT NULL AND EXISTS (SELECT 1 FROM dbo.ProjectTimelineMetricSettings WHERE ProjectID = @ProjectID) THEN 1 ELSE 0 END;
|
||||
|
||||
SELECT MetricTypeID, ProjectID, MetricName, Description, MinValue, MaxValue, DefaultValue, SortOrder, IsActive
|
||||
FROM dbo.SceneMetricTypes mt
|
||||
WHERE mt.IsActive = 1
|
||||
AND (mt.ProjectID IS NULL OR mt.ProjectID = @ProjectID)
|
||||
AND
|
||||
(
|
||||
@ProjectID IS NULL
|
||||
OR (@HasMetricSettings = 1 AND EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM dbo.ProjectTimelineMetricSettings ptms
|
||||
WHERE ptms.ProjectID = @ProjectID AND ptms.MetricTypeID = mt.MetricTypeID
|
||||
))
|
||||
OR (@HasMetricSettings = 0 AND mt.MetricName IN
|
||||
(
|
||||
N'Overall Intensity', N'Tension', N'Emotional Weight', N'Action', N'Darkness',
|
||||
N'Romance', N'Sexual Charge', N'Violence', N'Hope / Lightness', N'Comedy', N'Mystery'
|
||||
))
|
||||
)
|
||||
ORDER BY mt.SortOrder, mt.MetricName;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.SceneMetric_EnsureDefaults
|
||||
@SceneID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @ProjectID int;
|
||||
|
||||
SELECT @ProjectID = b.ProjectID
|
||||
FROM dbo.Scenes s
|
||||
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||
WHERE s.SceneID = @SceneID;
|
||||
|
||||
DECLARE @HasMetricSettings bit =
|
||||
CASE WHEN EXISTS (SELECT 1 FROM dbo.ProjectTimelineMetricSettings WHERE ProjectID = @ProjectID) THEN 1 ELSE 0 END;
|
||||
|
||||
INSERT dbo.SceneMetricValues (SceneID, MetricTypeID, Value, Notes)
|
||||
SELECT @SceneID, mt.MetricTypeID, mt.DefaultValue, NULL
|
||||
FROM dbo.SceneMetricTypes mt
|
||||
WHERE mt.IsActive = 1
|
||||
AND (mt.ProjectID IS NULL OR mt.ProjectID = @ProjectID)
|
||||
AND
|
||||
(
|
||||
(@HasMetricSettings = 1 AND EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM dbo.ProjectTimelineMetricSettings ptms
|
||||
WHERE ptms.ProjectID = @ProjectID AND ptms.MetricTypeID = mt.MetricTypeID
|
||||
))
|
||||
OR (@HasMetricSettings = 0 AND mt.MetricName IN
|
||||
(
|
||||
N'Overall Intensity', N'Tension', N'Emotional Weight', N'Action', N'Darkness',
|
||||
N'Romance', N'Sexual Charge', N'Violence', N'Hope / Lightness', N'Comedy', N'Mystery'
|
||||
))
|
||||
)
|
||||
AND NOT EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM dbo.SceneMetricValues smv
|
||||
WHERE smv.SceneID = @SceneID AND smv.MetricTypeID = mt.MetricTypeID
|
||||
);
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.SceneMetric_ListByScene
|
||||
@SceneID int
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
EXEC dbo.SceneMetric_EnsureDefaults @SceneID = @SceneID;
|
||||
|
||||
DECLARE @ProjectID int;
|
||||
|
||||
SELECT @ProjectID = b.ProjectID
|
||||
FROM dbo.Scenes s
|
||||
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||
WHERE s.SceneID = @SceneID;
|
||||
|
||||
DECLARE @HasMetricSettings bit =
|
||||
CASE WHEN EXISTS (SELECT 1 FROM dbo.ProjectTimelineMetricSettings WHERE ProjectID = @ProjectID) THEN 1 ELSE 0 END;
|
||||
|
||||
SELECT mt.MetricTypeID, mt.ProjectID, mt.MetricName, mt.Description, mt.MinValue, mt.MaxValue,
|
||||
mt.DefaultValue, mt.SortOrder, mt.IsActive, smv.SceneID, smv.Value, smv.Notes
|
||||
FROM dbo.SceneMetricTypes mt
|
||||
INNER JOIN dbo.SceneMetricValues smv ON smv.MetricTypeID = mt.MetricTypeID AND smv.SceneID = @SceneID
|
||||
WHERE mt.IsActive = 1
|
||||
AND
|
||||
(
|
||||
(@HasMetricSettings = 1 AND EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM dbo.ProjectTimelineMetricSettings ptms
|
||||
WHERE ptms.ProjectID = @ProjectID AND ptms.MetricTypeID = mt.MetricTypeID
|
||||
))
|
||||
OR (@HasMetricSettings = 0 AND mt.MetricName IN
|
||||
(
|
||||
N'Overall Intensity', N'Tension', N'Emotional Weight', N'Action', N'Darkness',
|
||||
N'Romance', N'Sexual Charge', N'Violence', N'Hope / Lightness', N'Comedy', N'Mystery'
|
||||
))
|
||||
)
|
||||
ORDER BY mt.SortOrder, mt.MetricName;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Analytics_StoryHealth_Remainder
|
||||
@ProjectID int,
|
||||
@BookID int = NULL,
|
||||
@StartChapterNumber decimal(10,2) = NULL,
|
||||
@EndChapterNumber decimal(10,2) = NULL,
|
||||
@StartSceneNumber decimal(10,2) = NULL,
|
||||
@EndSceneNumber decimal(10,2) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
;WITH EventScope AS
|
||||
(
|
||||
SELECT pt.PlotThreadID, MAX(nso.GlobalSceneIndex) AS LastTouchedIndex, MAX(nso.SceneID) AS LastTouchedSceneID,
|
||||
MAX(CONCAT(N'Ch ', nso.ChapterNumber, N' / Scene ', nso.SceneNumber, N': ', nso.SceneTitle)) AS LastTouchedSceneLabel
|
||||
FROM dbo.PlotThreads pt
|
||||
INNER JOIN dbo.ThreadEvents te ON te.PlotThreadID = pt.PlotThreadID
|
||||
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID
|
||||
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||
GROUP BY pt.PlotThreadID
|
||||
)
|
||||
SELECT pt.PlotThreadID, pt.ThreadTitle, pl.PlotLineID, pl.PlotLineName, pt.Importance,
|
||||
ts.StatusName AS ThreadStatusName, ts.IsOpenStatus, ts.IsResolvedStatus,
|
||||
pt.IntroducedSceneID, pt.PlannedResolutionSceneID, pt.ActualResolutionSceneID,
|
||||
EventScope.LastTouchedSceneID, EventScope.LastTouchedSceneLabel,
|
||||
CASE WHEN ts.IsOpenStatus = 1 AND pt.Importance >= 8 THEN 1 ELSE 0 END AS IsHighImportanceOpen,
|
||||
CASE WHEN ts.IsOpenStatus = 1 AND EventScope.LastTouchedIndex IS NOT NULL
|
||||
AND (SELECT MAX(GlobalSceneIndex) FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID)) - EventScope.LastTouchedIndex > 8 THEN 1 ELSE 0 END AS IsDormant,
|
||||
CASE WHEN EXISTS (
|
||||
SELECT 1 FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||
WHERE te.PlotThreadID = pt.PlotThreadID AND tet.TypeName = N'Clue Planted'
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1 FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||
WHERE te.PlotThreadID = pt.PlotThreadID AND tet.TypeName IN (N'Payoff', N'Question Answered', N'Revealed', N'Resolved')
|
||||
) THEN 1 ELSE 0 END AS HasClueWithoutPayoff,
|
||||
CASE WHEN EXISTS (
|
||||
SELECT 1 FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||
WHERE te.PlotThreadID = pt.PlotThreadID AND tet.TypeName = N'Question Raised'
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1 FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
|
||||
WHERE te.PlotThreadID = pt.PlotThreadID AND tet.TypeName = N'Question Answered'
|
||||
) THEN 1 ELSE 0 END AS HasQuestionWithoutAnswer
|
||||
FROM dbo.PlotThreads pt
|
||||
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
|
||||
INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID
|
||||
LEFT JOIN EventScope ON EventScope.PlotThreadID = pt.PlotThreadID
|
||||
WHERE pl.ProjectID = @ProjectID AND pt.IsArchived = 0 AND pl.IsArchived = 0
|
||||
AND (@BookID IS NULL OR pl.BookID IS NULL OR pl.BookID = @BookID)
|
||||
ORDER BY ts.IsOpenStatus DESC, pt.Importance DESC, pl.PlotLineName, pt.ThreadTitle;
|
||||
|
||||
;WITH LastAssetEvent AS
|
||||
(
|
||||
SELECT ae.StoryAssetID, MAX(nso.GlobalSceneIndex) AS LastEventIndex, MAX(ae.SceneID) AS LastEventSceneID,
|
||||
MAX(CONCAT(N'Ch ', nso.ChapterNumber, N' / Scene ', nso.SceneNumber, N': ', nso.SceneTitle)) AS LastEventSceneLabel
|
||||
FROM dbo.AssetEvents ae
|
||||
INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = ae.SceneID
|
||||
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||
GROUP BY ae.StoryAssetID
|
||||
)
|
||||
SELECT sa.StoryAssetID, sa.AssetName, ak.KindName, sa.Importance, sa.CurrentStateID,
|
||||
ast.StateName AS CurrentStateName, sa.CurrentLocationID, location.LocationPath AS CurrentLocationPath,
|
||||
sa.IsResolved, LastAssetEvent.LastEventSceneID, LastAssetEvent.LastEventSceneLabel,
|
||||
CASE WHEN EXISTS (SELECT 1 FROM dbo.AssetDependencies ad WHERE ad.IsArchived = 0 AND (ad.SourceAssetID = sa.StoryAssetID OR ad.TargetAssetID = sa.StoryAssetID)) THEN 1 ELSE 0 END AS HasDependencies,
|
||||
CASE WHEN EXISTS (
|
||||
SELECT 1 FROM dbo.ContinuityWarnings cw
|
||||
INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
|
||||
WHERE cw.ProjectID = @ProjectID AND cw.EntityType IN (N'StoryAsset', N'AssetDependency') AND cw.EntityID IN (sa.StoryAssetID)
|
||||
AND wt.TypeName LIKE N'Asset%' AND cw.IsDismissed = 0 AND cw.IsIntentional = 0
|
||||
) THEN 1 ELSE 0 END AS HasDependencyWarnings
|
||||
FROM dbo.StoryAssets sa
|
||||
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
|
||||
LEFT JOIN dbo.LocationPaths location ON location.LocationID = sa.CurrentLocationID
|
||||
LEFT JOIN LastAssetEvent ON LastAssetEvent.StoryAssetID = sa.StoryAssetID
|
||||
WHERE sa.ProjectID = @ProjectID AND sa.IsArchived = 0
|
||||
ORDER BY sa.IsResolved, sa.Importance DESC, sa.AssetName;
|
||||
|
||||
SELECT ws.SeverityName, COUNT(*) AS WarningCount
|
||||
FROM dbo.ContinuityWarnings cw
|
||||
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||
WHERE cw.ProjectID = @ProjectID AND (@BookID IS NULL OR cw.BookID = @BookID)
|
||||
AND cw.IsDismissed = 0 AND cw.IsIntentional = 0
|
||||
GROUP BY ws.SeverityName, ws.SortOrder
|
||||
ORDER BY ws.SortOrder DESC;
|
||||
|
||||
SELECT TOP (12) wt.TypeName AS WarningTypeName, COUNT(*) AS WarningCount
|
||||
FROM dbo.ContinuityWarnings cw
|
||||
INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID
|
||||
WHERE cw.ProjectID = @ProjectID AND (@BookID IS NULL OR cw.BookID = @BookID)
|
||||
AND cw.IsDismissed = 0 AND cw.IsIntentional = 0
|
||||
GROUP BY wt.TypeName
|
||||
ORDER BY COUNT(*) DESC, wt.TypeName;
|
||||
|
||||
SELECT TOP (12) cw.SceneID, s.SceneNumber, s.SceneTitle, c.ChapterNumber, b.BookTitle,
|
||||
COUNT(*) AS WarningCount, MAX(ws.SortOrder) AS HighestSeveritySort
|
||||
FROM dbo.ContinuityWarnings cw
|
||||
INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = cw.SceneID
|
||||
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||
WHERE cw.ProjectID = @ProjectID AND cw.SceneID IS NOT NULL AND (@BookID IS NULL OR cw.BookID = @BookID)
|
||||
AND cw.IsDismissed = 0 AND cw.IsIntentional = 0
|
||||
GROUP BY cw.SceneID, s.SceneNumber, s.SceneTitle, c.ChapterNumber, b.BookTitle
|
||||
ORDER BY COUNT(*) DESC, MAX(ws.SortOrder) DESC;
|
||||
|
||||
;WITH ScopeScenes AS
|
||||
(
|
||||
SELECT nso.SceneID, nso.BookID, nso.BookTitle, nso.ChapterID, nso.ChapterNumber, nso.SceneNumber, nso.SceneTitle,
|
||||
COALESCE(s.POVCharacterID, c.POVCharacterID) AS POVCharacterID
|
||||
FROM dbo.NarrativeSceneOrder nso
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = nso.SceneID
|
||||
INNER JOIN dbo.Chapters c ON c.ChapterID = nso.ChapterID
|
||||
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||
)
|
||||
SELECT COALESCE(ch.CharacterName, N'No POV') AS POVName, ss.POVCharacterID, COUNT(*) AS SceneCount
|
||||
FROM ScopeScenes ss
|
||||
LEFT JOIN dbo.Characters ch ON ch.CharacterID = ss.POVCharacterID
|
||||
GROUP BY ss.POVCharacterID, ch.CharacterName
|
||||
ORDER BY COUNT(*) DESC, POVName;
|
||||
|
||||
;WITH ScopeScenes AS
|
||||
(
|
||||
SELECT nso.SceneID, nso.ChapterID
|
||||
FROM dbo.NarrativeSceneOrder nso
|
||||
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||
)
|
||||
SELECT c.ChapterID, c.ChapterNumber, c.ChapterTitle, COUNT(DISTINCT COALESCE(s.POVCharacterID, c.POVCharacterID)) AS POVCount
|
||||
FROM dbo.Chapters c
|
||||
INNER JOIN dbo.Scenes s ON s.ChapterID = c.ChapterID
|
||||
WHERE c.ChapterID IN (SELECT ChapterID FROM ScopeScenes)
|
||||
GROUP BY c.ChapterID, c.ChapterNumber, c.ChapterTitle
|
||||
HAVING COUNT(DISTINCT COALESCE(s.POVCharacterID, c.POVCharacterID)) > 1
|
||||
ORDER BY c.ChapterNumber;
|
||||
|
||||
SELECT ch.CharacterID, ch.CharacterName, COUNT(DISTINCT sc.SceneID) AS SceneCount
|
||||
FROM dbo.Characters ch
|
||||
LEFT JOIN dbo.SceneCharacters sc ON sc.CharacterID = ch.CharacterID
|
||||
LEFT JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID
|
||||
WHERE ch.ProjectID = @ProjectID AND ch.IsArchived = 0 AND (@BookID IS NULL OR nso.BookID = @BookID OR sc.SceneID IS NULL)
|
||||
GROUP BY ch.CharacterID, ch.CharacterName
|
||||
ORDER BY COUNT(DISTINCT sc.SceneID) DESC, ch.CharacterName;
|
||||
|
||||
;WITH SceneCharacterCounts AS
|
||||
(
|
||||
SELECT nso.SceneID, nso.SceneNumber, nso.SceneTitle, nso.ChapterNumber, COUNT(sc.SceneCharacterID) AS CharacterCount
|
||||
FROM dbo.NarrativeSceneOrder nso
|
||||
LEFT JOIN dbo.SceneCharacters sc ON sc.SceneID = nso.SceneID
|
||||
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||
GROUP BY nso.SceneID, nso.SceneNumber, nso.SceneTitle, nso.ChapterNumber
|
||||
)
|
||||
SELECT TOP (12) * FROM SceneCharacterCounts WHERE CharacterCount >= 5 ORDER BY CharacterCount DESC, ChapterNumber, SceneNumber;
|
||||
|
||||
;WITH ScopeScenes AS
|
||||
(
|
||||
SELECT nso.SceneID, s.PrimaryLocationID
|
||||
FROM dbo.NarrativeSceneOrder nso
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = nso.SceneID
|
||||
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||
)
|
||||
SELECT COALESCE(lp.LocationName, N'No location') AS LocationName, lp.LocationID, COALESCE(lp.LocationPath, N'No location') AS LocationPath,
|
||||
COUNT(*) AS SceneCount
|
||||
FROM ScopeScenes ss
|
||||
LEFT JOIN dbo.LocationPaths lp ON lp.LocationID = ss.PrimaryLocationID
|
||||
GROUP BY lp.LocationID, lp.LocationName, lp.LocationPath
|
||||
ORDER BY COUNT(*) DESC, LocationName;
|
||||
END;
|
||||
GO
|
||||
|
||||
CREATE OR ALTER PROCEDURE dbo.Analytics_StoryHealth
|
||||
@ProjectID int,
|
||||
@BookID int = NULL,
|
||||
@StartChapterNumber decimal(10,2) = NULL,
|
||||
@EndChapterNumber decimal(10,2) = NULL,
|
||||
@StartSceneNumber decimal(10,2) = NULL,
|
||||
@EndSceneNumber decimal(10,2) = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
DECLARE @HasMetricSettings bit =
|
||||
CASE WHEN EXISTS (SELECT 1 FROM dbo.ProjectTimelineMetricSettings WHERE ProjectID = @ProjectID) THEN 1 ELSE 0 END;
|
||||
|
||||
;WITH ScopeScenes AS
|
||||
(
|
||||
SELECT nso.*
|
||||
FROM dbo.NarrativeSceneOrder nso
|
||||
WHERE nso.ProjectID = @ProjectID
|
||||
AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||
)
|
||||
SELECT ss.SceneID, ss.BookID, ss.BookTitle, ss.ChapterID, ss.ChapterNumber, ss.ChapterTitle,
|
||||
ss.SceneNumber, ss.SceneTitle, ss.GlobalSceneIndex, ss.BookSceneIndex, smt.MetricTypeID,
|
||||
smt.MetricName, smv.Value, smt.MinValue, smt.MaxValue,
|
||||
CASE WHEN smt.MaxValue = smt.MinValue THEN 0 ELSE CAST(((smv.Value - smt.MinValue) * 100.0) / (smt.MaxValue - smt.MinValue) AS int) END AS [Percent]
|
||||
FROM ScopeScenes ss
|
||||
INNER JOIN dbo.SceneMetricValues smv ON smv.SceneID = ss.SceneID
|
||||
INNER JOIN dbo.SceneMetricTypes smt ON smt.MetricTypeID = smv.MetricTypeID
|
||||
WHERE smt.IsActive = 1
|
||||
AND
|
||||
(
|
||||
(@HasMetricSettings = 1 AND EXISTS
|
||||
(
|
||||
SELECT 1
|
||||
FROM dbo.ProjectTimelineMetricSettings ptms
|
||||
WHERE ptms.ProjectID = @ProjectID AND ptms.MetricTypeID = smt.MetricTypeID
|
||||
))
|
||||
OR (@HasMetricSettings = 0 AND smt.MetricName IN
|
||||
(
|
||||
N'Overall Intensity', N'Tension', N'Emotional Weight', N'Action', N'Romance',
|
||||
N'Violence', N'Sexual Charge', N'Mystery', N'Hope / Lightness', N'Comedy', N'Darkness'
|
||||
))
|
||||
)
|
||||
ORDER BY smt.SortOrder, ss.GlobalSceneIndex;
|
||||
|
||||
;WITH ScopeScenes AS
|
||||
(
|
||||
SELECT nso.SceneID, nso.ChapterID
|
||||
FROM dbo.NarrativeSceneOrder nso
|
||||
WHERE nso.ProjectID = @ProjectID
|
||||
AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||
)
|
||||
SELECT rs.RevisionStatusID, rs.StatusName, COUNT(s.SceneID) AS SceneCount,
|
||||
SUM(CASE WHEN c.ChapterID IS NOT NULL THEN 1 ELSE 0 END) AS ChapterCount
|
||||
FROM dbo.RevisionStatuses rs
|
||||
LEFT JOIN dbo.Scenes s ON s.RevisionStatusID = rs.RevisionStatusID AND s.SceneID IN (SELECT SceneID FROM ScopeScenes)
|
||||
LEFT JOIN dbo.Chapters c ON c.RevisionStatusID = rs.RevisionStatusID AND c.ChapterID IN (SELECT DISTINCT ChapterID FROM ScopeScenes)
|
||||
GROUP BY rs.RevisionStatusID, rs.StatusName, rs.SortOrder
|
||||
ORDER BY rs.SortOrder;
|
||||
|
||||
;WITH ScopeScenes AS
|
||||
(
|
||||
SELECT nso.SceneID, nso.BookID, nso.BookTitle, nso.ChapterID, nso.ChapterNumber, nso.SceneNumber, nso.SceneTitle
|
||||
FROM dbo.NarrativeSceneOrder nso
|
||||
WHERE nso.ProjectID = @ProjectID
|
||||
AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||
)
|
||||
SELECT ss.SceneID, ss.BookID, ss.BookTitle, ss.ChapterID, ss.ChapterNumber, ss.SceneNumber, ss.SceneTitle,
|
||||
rs.StatusName AS RevisionStatusName
|
||||
FROM ScopeScenes ss
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = ss.SceneID
|
||||
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
|
||||
WHERE rs.StatusName IN (N'Needs Work', N'Cut Candidate', N'Drafted')
|
||||
ORDER BY rs.SortOrder, ss.BookID, ss.ChapterNumber, ss.SceneNumber;
|
||||
|
||||
;WITH ScopeScenes AS
|
||||
(
|
||||
SELECT nso.SceneID
|
||||
FROM dbo.NarrativeSceneOrder nso
|
||||
WHERE nso.ProjectID = @ProjectID
|
||||
AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||
),
|
||||
Total AS (SELECT COUNT(*) AS TotalScenes FROM ScopeScenes)
|
||||
SELECT spt.ScenePurposeTypeID, spt.PurposeName, COUNT(sp.SceneID) AS SceneCount,
|
||||
CASE WHEN Total.TotalScenes = 0 THEN 0 ELSE CAST(COUNT(sp.SceneID) * 100.0 / Total.TotalScenes AS decimal(5,1)) END AS PercentOfScenes
|
||||
FROM dbo.ScenePurposeTypes spt
|
||||
CROSS JOIN Total
|
||||
LEFT JOIN dbo.ScenePurposes sp ON sp.ScenePurposeTypeID = spt.ScenePurposeTypeID AND sp.SceneID IN (SELECT SceneID FROM ScopeScenes)
|
||||
WHERE spt.IsArchived = 0
|
||||
GROUP BY spt.ScenePurposeTypeID, spt.PurposeName, spt.SortOrder, Total.TotalScenes
|
||||
ORDER BY spt.SortOrder;
|
||||
|
||||
;WITH ScopeScenes AS
|
||||
(
|
||||
SELECT nso.SceneID, nso.BookID, nso.BookTitle, nso.ChapterID, nso.ChapterNumber, nso.SceneNumber, nso.SceneTitle
|
||||
FROM dbo.NarrativeSceneOrder nso
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = nso.SceneID
|
||||
WHERE nso.ProjectID = @ProjectID
|
||||
AND (@BookID IS NULL OR nso.BookID = @BookID)
|
||||
AND (@StartChapterNumber IS NULL OR nso.ChapterNumber >= @StartChapterNumber)
|
||||
AND (@EndChapterNumber IS NULL OR nso.ChapterNumber <= @EndChapterNumber)
|
||||
AND (@StartSceneNumber IS NULL OR nso.SceneNumber >= @StartSceneNumber)
|
||||
AND (@EndSceneNumber IS NULL OR nso.SceneNumber <= @EndSceneNumber)
|
||||
AND (
|
||||
NOT EXISTS (SELECT 1 FROM dbo.ScenePurposes sp WHERE sp.SceneID = nso.SceneID)
|
||||
OR ISNULL(NULLIF(LTRIM(RTRIM(s.SceneOutcomeNotes)), N''), N'') = N''
|
||||
)
|
||||
)
|
||||
SELECT ss.SceneID, ss.BookID, ss.BookTitle, ss.ChapterID, ss.ChapterNumber, ss.SceneNumber, ss.SceneTitle,
|
||||
CASE WHEN NOT EXISTS (SELECT 1 FROM dbo.ScenePurposes sp WHERE sp.SceneID = ss.SceneID) THEN 1 ELSE 0 END AS MissingPurpose,
|
||||
CASE WHEN ISNULL(NULLIF(LTRIM(RTRIM(s.SceneOutcomeNotes)), N''), N'') = N'' THEN 1 ELSE 0 END AS MissingOutcome
|
||||
FROM ScopeScenes ss
|
||||
INNER JOIN dbo.Scenes s ON s.SceneID = ss.SceneID
|
||||
ORDER BY ss.BookID, ss.ChapterNumber, ss.SceneNumber;
|
||||
|
||||
EXEC dbo.Analytics_StoryHealth_Remainder @ProjectID, @BookID, @StartChapterNumber, @EndChapterNumber, @StartSceneNumber, @EndSceneNumber;
|
||||
END;
|
||||
GO
|
||||
@ -14,6 +14,11 @@ public sealed class ProjectEditViewModel
|
||||
public string ProjectName { get; set; } = string.Empty;
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
[Display(Name = "Genre / Metric Preset")]
|
||||
public string GenreMetricPresetKey { get; set; } = "Custom";
|
||||
|
||||
public IReadOnlyList<SelectListItem> GenreMetricPresetOptions { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class ProjectDetailViewModel
|
||||
|
||||
@ -23,6 +23,14 @@
|
||||
<label asp-for="Description" class="form-label"></label>
|
||||
<textarea asp-for="Description" class="form-control" rows="5"></textarea>
|
||||
</div>
|
||||
@if (Model.ProjectID == 0)
|
||||
{
|
||||
<div class="mb-3">
|
||||
<label asp-for="GenreMetricPresetKey" class="form-label"></label>
|
||||
<select asp-for="GenreMetricPresetKey" asp-items="Model.GenreMetricPresetOptions" class="form-select"></select>
|
||||
<div class="form-text">Choose a starting set of scene metrics for this project. You can adjust timeline metrics later in Timeline Settings.</div>
|
||||
</div>
|
||||
}
|
||||
<button class="btn btn-primary" type="submit">Save project</button>
|
||||
</form>
|
||||
|
||||
|
||||
@ -100,7 +100,7 @@
|
||||
</div>
|
||||
<span class="soft-count">@Model.MetricOptions.Count metrics</span>
|
||||
</div>
|
||||
<p class="muted">These metrics appear on the timeline graph only. Story Health can continue using the full metric set.</p>
|
||||
<p class="muted">These metrics appear on the timeline graph and Story Health metric shape for this project.</p>
|
||||
<span asp-validation-for="SelectedMetricTypeIDs" class="text-danger"></span>
|
||||
<div class="timeline-settings-grid timeline-metric-settings-grid">
|
||||
@foreach (var metric in Model.MetricOptions)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user