diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..8b211e3 --- /dev/null +++ b/Pipfile @@ -0,0 +1,14 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +pytest = "*" +coverage = "*" +pytest-cov = "*" + +[dev-packages] + +[requires] +python_version = "3.10" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..41a6443 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,237 @@ +{ + "_meta": { + "hash": { + "sha256": "86787bc83b48d0f9ff34a488f1dfd698c217e795b3018235f3a82cc26a24efd4" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.10" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "coverage": { + "extras": [ + "toml" + ], + "hashes": [ + "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5", + "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e", + "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab", + "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a", + "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82", + "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", + "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893", + "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de", + "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969", + "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601", + "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de", + "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05", + "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3", + "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad", + "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841", + "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847", + "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52", + "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11", + "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", + "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73", + "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415", + "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745", + "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b", + "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785", + "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4", + "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48", + "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2", + "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b", + "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040", + "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0", + "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866", + "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1", + "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf", + "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b", + "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d", + "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115", + "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2", + "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9", + "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43", + "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc", + "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa", + "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1", + "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088", + "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c", + "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777", + "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623", + "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268", + "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186", + "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e", + "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007", + "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7", + "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547", + "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3", + "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc", + "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37", + "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721", + "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552", + "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca", + "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497", + "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46", + "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2", + "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0", + "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44", + "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1", + "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4", + "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479", + "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e", + "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2", + "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e", + "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef", + "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0", + "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad", + "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075", + "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f", + "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d", + "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca", + "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd", + "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287", + "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f", + "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0", + "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996", + "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d", + "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd", + "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31", + "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740", + "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591", + "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be", + "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f", + "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d", + "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836", + "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d", + "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==7.11.0" + }, + "exceptiongroup": { + "hashes": [ + "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", + "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88" + ], + "markers": "python_version < '3.11'", + "version": "==1.3.0" + }, + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", + "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==8.4.2" + }, + "pytest-cov": { + "hashes": [ + "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", + "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==7.0.0" + }, + "tomli": { + "hashes": [ + "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", + "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", + "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", + "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", + "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", + "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", + "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", + "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", + "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", + "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", + "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", + "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", + "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", + "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", + "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", + "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", + "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", + "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", + "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", + "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", + "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", + "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", + "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", + "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", + "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", + "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", + "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", + "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", + "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", + "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", + "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", + "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", + "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", + "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", + "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", + "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", + "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", + "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", + "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", + "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", + "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", + "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876" + ], + "markers": "python_version < '3.11'", + "version": "==2.3.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", + "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" + ], + "markers": "python_version < '3.13'", + "version": "==4.15.0" + } + }, + "develop": {} +} diff --git a/README.md b/README.md index 6022e0e..e372a2b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,87 @@ -# Python Package Exercise +# Fortune Luck Predictor -An exercise to create a Python package, build it, test it, distribute it, and use it. See [instructions](./instructions.md) for details. +A Python package that returns lucky numbers, fortunes, and horoscope-like results. + +## What is it? + +Fortune Luck Predictor provides: +- Fortunes: Random fortune cookie-style messages +- Future Predictions: Predicts career, marriage age, number of kids, and success level +- Day Predictions: Get day-specific fortunes for any day of the week +- Compatibility Scores: Calculate compatibility between two names +- Lucky Numbers: Generate weighted lucky numbers based on the current date and time + +## Installation + +From PyPI: +```bash +pip install fortuneluckpredictor +``` + +From source: +```bash +git clone https://github.com/swe-students-fall2025/3-python-package-team_boreal.git +cd 3-python-package-team_boreal +pip install -e . +``` + +## How to Run + +### Interactive Mode + +Run the package as a module: +```bash +python -m fortuneluckpredictor +``` + +Or if you need to set PYTHONPATH: +```bash +PYTHONPATH=src python -m fortuneluckpredictor +``` + +This launches an interactive menu with all available functions. + +### Using as a Package + +Import and use the functions in your Python code: + +```python +from fortuneluckpredictor.fortune import get_fortune +from fortuneluckpredictor.futurePredictions import futurePrediction +from fortuneluckpredictor.predictDay import predict_day +from fortuneluckpredictor.compatibility_score import compatibility_score +from fortuneluckpredictor.luckyNumber import get_lucky_number + +# Get a fortune +print(get_fortune()) + +# Predict your future +print(futurePrediction("Alice")) + +# Predict a day +print(predict_day("Monday")) + +# Check compatibility +print(compatibility_score("Alice", "Bob")) + +# Get a lucky number +print(get_lucky_number((1, 100))) +``` + +## Development + +Set up the development environment: +```bash +pipenv install --dev +pipenv shell +pip install -e . +``` + +Run tests: +```bash +pytest tests/ +``` + +## Links + +- GitHub: https://github.com/swe-students-fall2025/3-python-package-team_boreal diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5195a2a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "fortuneluckpredictor" +version = "0.1.10" +authors = [ + { name="Pranathi Chinthalapani", email="prc9852@nyu.edu" }, + { name="Sam Rawdon", email="sr6360@nyu.edu" }, + { name="May Zhou", email="zz4206@nyu.edu" }, + { name="Hanjun Deng", email=hd2432@nyu.edu" } +] +description = "Fortune & luck predictor. Returns lucky numbers, fortunes, and horoscope-like results." +readme = "README.md" +keywords = ["python", "package", "build", "cookie", "fun", "guess", "luck", "fortune"] + +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: OS Independent", +] + +license = {text = "GPL-3.0"} +license-files = ["LICEN[CS]E*"] + +[project.optional-dependencies] +dev = ["pytest"] + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] + +[project.urls] +Homepage = "https://github.com/swe-students-fall2025/3-python-package-team_boreal" +Repository = "https://github.com/swe-students-fall2025/3-python-package-team_boreal.git" +Issues = "https://github.com/swe-students-fall2025/3-python-package-team_boreal/issues" diff --git a/src/fortuneluckpredictor/__init__.py b/src/fortuneluckpredictor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/fortuneluckpredictor/__main__.py b/src/fortuneluckpredictor/__main__.py new file mode 100644 index 0000000..a500210 --- /dev/null +++ b/src/fortuneluckpredictor/__main__.py @@ -0,0 +1,56 @@ +from .fortune import get_fortune +from .futurePredictions import futurePrediction +from .compatibility_score import compatibility_score +from .luckyNumber import get_lucky_number +from .predictDay import predict_day + +def main() -> None: + + print("\nโœจ๐Ÿ”ฎโœจ==============================โœจ๐Ÿ”ฎโœจ") + print(" Welcome to Fortune Luck Predictor! ") + print("โœจ๐Ÿ”ฎโœจ==============================โœจ๐Ÿ”ฎโœจ") + print(" (ใฅ๏ฝกโ—•โ€ฟโ€ฟโ—•๏ฝก)ใฅ โœจ ๐Ÿง™โ€โ™‚๏ธ ๐Ÿ€ ๐Ÿฆ„ ") + + while True: + print("1. Get your fortune โœจ") + print("2. Predict your future ๐Ÿ”ฎ") + print("3. Predict your day โ˜€๏ธ") + print("4. Compatibility score ๐Ÿ’ž") + print("5. Get your lucky number ๐Ÿ€") + print("6. Exit ๐Ÿšช") + choice = input("Choose an option (1-6): ").strip() + + if choice == "1": + print("\nYour fortune:") + print(get_fortune()) + elif choice == "2": + name = input("Enter your name: ").strip() + print("\n" + futurePrediction(name)) + elif choice == "3": + day = input("Enter a day of the week: ").strip() + try: + print("\n" + predict_day(day or None)) + except Exception as e: + print(f"Error: {e}") + elif choice == "4": + name1 = input("Enter the first name: ").strip() + name2 = input("Enter the second name: ").strip() + print("\n" + compatibility_score(name1, name2)) + elif choice == "5": + print("Your lucky number:") + print(get_lucky_number()) + elif choice == "6": + print("Goodbye! โœจ") + break + else: + print("Invalid choice. Please enter a number from 1 to 6.") + + continue_prompt = input("\nWould you like to try another option? (yes/no): ").strip().lower() + if continue_prompt not in ('yes', 'y'): + print("Goodbye! โœจ") + break + + print("\n") + +if __name__ == "__main__": + main() diff --git a/src/fortuneluckpredictor/compatibility_score.py b/src/fortuneluckpredictor/compatibility_score.py new file mode 100644 index 0000000..1a16ca2 --- /dev/null +++ b/src/fortuneluckpredictor/compatibility_score.py @@ -0,0 +1,31 @@ +def compatibility_score(name1: str, name2: str) -> str: + score = 0 + vowels = "aeiou" + c = [] # each vowel's count + n1 = name1.lower() + n2 = name2.lower() + for v in vowels: + c1 = n1.count(v) + c2 = n2.count(v) + n = abs(c1 - c2) + c.append(n) + + # Add vowel counts next to each other together until one digit remains + while(len(c) > 1): + new_c = [] + for i in range(len(c) - 1): + new_c.append((c[i] + c[i+1]) % 10) + c = new_c + + score = c[0] + # compatibility message based on final score + if score <= 2: + message = "Perfect match โ€” pure harmony! ๐Ÿ’ž" + elif score <= 4: + message = "You two vibe really well! โค๏ธ" + elif score <= 7: + message = "There's potential โ€” stay open and see where it goes! ๐ŸŒˆ" + else: + message = "You'll meet someone wonderful who complements you perfectly! ๐Ÿ’ซ" + + return f"Compatibility score: {score}. {message}" \ No newline at end of file diff --git a/src/fortuneluckpredictor/fortune.py b/src/fortuneluckpredictor/fortune.py new file mode 100644 index 0000000..d5d64e4 --- /dev/null +++ b/src/fortuneluckpredictor/fortune.py @@ -0,0 +1,30 @@ +import random + +fortunes = [ + "Your resume will land on the perfect Meta recruiter's desk this week.", + "A Meta engineer will soon endorse your skills and open a new door.", + "The next coding interview you take will feel like designing the future of social connections.", + "Your grind on system design is about to pay off with a Reality Labs breakthrough.", + "A friendly referral will unlock the Meta pipeline you've been eyeing.", + "An unexpected DM from a Meta recruiter will kickstart your big tech journey.", + "Your portfolio will impress a Meta hiring manager far more than you expect.", + "A well-timed leetcode streak will boost your confidence for the Meta onsite.", + "Soon you'll celebrate a Meta offer with your closest supporters.", + "The next recruiter call you accept will change the trajectory of your career in the metaverse.", + "A spontaneous trip will lead to a story you retell for years.", + "Your curiosity will uncover a hobby that becomes a passion.", + "A long-standing question will be answered in the most unexpected way.", + "You will reconnect with someone who inspires you to dream bigger.", + "Small daily habits will compound into a breakthrough moment.", + "A random compliment will brighten your day and boost your confidence.", + "Your next challenge will reveal strength you didn't know you had.", + "A creative idea will blossom into a project that excites others.", + "Soon you will realize you are exactly where you need to be.", + "An opportunity to help someone will come, and your kindness will be remembered.", + ] + +def get_fortune(cookie: bool = True) -> str: + if cookie: + return f"๐Ÿช {random.choice(fortunes)}" + else: + return random.choice(fortunes) \ No newline at end of file diff --git a/src/fortuneluckpredictor/futurePredictions.py b/src/fortuneluckpredictor/futurePredictions.py new file mode 100644 index 0000000..e528830 --- /dev/null +++ b/src/fortuneluckpredictor/futurePredictions.py @@ -0,0 +1,31 @@ +import random + +def futurePrediction(name1: str) -> str: + careers = [ + "software engineer", "doctor", + "lawyer", "teacher", "artist", + "entrepreneur", "scientist", + "musician", "architect", "chef", + "engineer", "journalist", + "psychologist", "designer", "consultant", + "nurse", "researcher", "manager", + "analyst", "inventor" + ] + + marriage_ages = list(range(25, 40)) + kids_counts = list(range(0, 4)) + success_levels = ["very successful", "successful", + "somewhat successful", "moderately successful"] + + name = sum(ord(c) for c in name1) + + random.seed(name) + career = random.choice(careers) + marriage_age = random.choice(marriage_ages) + kids_count = random.choice(kids_counts) + success = random.choice(success_levels) + + kids_str = "kids" if kids_count != 1 else "kid" + kids_phrase = f"with {kids_count} {kids_str}" if kids_count > 0 else "with no kids" + + return f"{name1} will be a {career} and get married at {marriage_age} {kids_phrase} and you will be {success}" \ No newline at end of file diff --git a/src/fortuneluckpredictor/luckyNumber.py b/src/fortuneluckpredictor/luckyNumber.py new file mode 100644 index 0000000..b9ae762 --- /dev/null +++ b/src/fortuneluckpredictor/luckyNumber.py @@ -0,0 +1,43 @@ +import random +import math +from datetime import datetime + +try: + from zoneinfo import ZoneInfo + _tz = ZoneInfo("America/New_York") +except Exception: + _tz = None + +def get_lucky_number(r=(1, 100)): + a, b = r + if a > b: + a, b = b, a + nums = list(range(a, b + 1)) + + now = datetime.now(_tz) if _tz else datetime.now() + seed = int(now.strftime("%Y%m%d")) + r = random.Random(seed) + + day = now.day + month = now.month + wday = now.isoweekday() + hour = now.hour or 24 + minute = now.minute or 60 + digits_sum = sum(int(x) for x in now.strftime("%Y%m%d%H%M")) + + bases = [day, month, wday, hour, minute, digits_sum % (b - a + 1) or 1] + center = sum(bases) / len(bases) + scale = max(1.0, (b - a) / 8.0) + + weights = [] + for n in nums: + w = 1.0 + math.exp(-abs(n - center) / scale) + w += 0.25 * sum(1 for base in bases if base and n % base == 0) + last_digit = int(str(n)[-1]) + w += 0.2 if last_digit == day % 10 else 0.0 + w += 0.2 if last_digit == month % 10 else 0.0 + w += 0.1 * (str(day) in str(n)) + w += 0.1 * (str(month) in str(n)) + weights.append(w) + + return r.choices(nums, weights=weights, k=1)[0] \ No newline at end of file diff --git a/src/fortuneluckpredictor/predictDay.py b/src/fortuneluckpredictor/predictDay.py new file mode 100644 index 0000000..432aee3 --- /dev/null +++ b/src/fortuneluckpredictor/predictDay.py @@ -0,0 +1,47 @@ +from .fortune import fortunes +import random +from datetime import datetime + +# Each day has its own "theme" or message style +DAY_MESSAGES = { + "monday": "Fresh energy fills your week โ€” set your intentions high.", + "tuesday": "Progress is building; stay focused and take bold steps.", + "wednesday": "Midweek clarity brings productive breakthroughs.", + "thursday": "Youโ€™re almost at the finish line โ€” stay sharp and consistent.", + "friday": "Celebrate your wins, no matter how small!", + "saturday": "Recharge your creative battery โ€” inspiration awaits.", + "sunday": "Plan lightly and dream big for the week ahead.", +} + +# Support abbreviations (Mon, Tue, etc.) +DAY_ALIASES = { + "mon": "monday", + "tue": "tuesday", "tues": "tuesday", + "wed": "wednesday", + "thu": "thursday", "thur": "thursday", "thurs": "thursday", + "fri": "friday", + "sat": "saturday", + "sun": "sunday", +} + +def predict_day(day: str | None = None) -> str: + # If no day provided, use today's day name + if not day or not day.strip(): + day = datetime.now().strftime("%A") + d = day.strip().lower() + + # Normalize to full day name + key = d if d in DAY_MESSAGES else DAY_ALIASES.get(d) + if not key: + raise ValueError(f"Unrecognized day name: '{day}'") + + message = DAY_MESSAGES[key] + + # Create a deterministic fortune based on today's date and day name + today = datetime.now().strftime("%Y-%m-%d") + seed_value = hash(today + key) % (2**32) + rng = random.Random(seed_value) + fortune_text = rng.choice(fortunes) + + # Format nicely + return f"{key.capitalize()}: {message} Fortune: {fortune_text}" \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_compatibility_score.py b/tests/test_compatibility_score.py new file mode 100644 index 0000000..6ef8593 --- /dev/null +++ b/tests/test_compatibility_score.py @@ -0,0 +1,109 @@ +import pytest +from src.fortuneluckpredictor import compatibility_score + +# Sample names for testing +@pytest.fixture +def sample_names(): + return [ + ("", "", "Compatibility score: 0. Perfect match โ€” pure harmony! ๐Ÿ’ž"), + ("hello", "hi", "Compatibility score: 4. You two vibe really well! โค๏ธ"), + ("moon", "train", "Compatibility score: 5. There's potential โ€” stay open and see where it goes! ๐ŸŒˆ"), + ("bee", "pool", "Compatibility score: 6. There's potential โ€” stay open and see where it goes! ๐ŸŒˆ"), + ("aaa", "eee", "Compatibility score: 5. There's potential โ€” stay open and see where it goes! ๐ŸŒˆ"), + ("hello", "hello", "Compatibility score: 0. Perfect match โ€” pure harmony! ๐Ÿ’ž"), + ("Michael", "Michelle", "Compatibility score: 5. There's potential โ€” stay open and see where it goes! ๐ŸŒˆ"), + ("James", "Emma", "Compatibility score: 0. Perfect match โ€” pure harmony! ๐Ÿ’ž"), + ("Isabella", "Alexander", "Compatibility score: 0. Perfect match โ€” pure harmony! ๐Ÿ’ž"), + ("William", "Olivia", "Compatibility score: 4. You two vibe really well! โค๏ธ"), + ("Sophia", "Daniel", "Compatibility score: 8. You'll meet someone wonderful who complements you perfectly! ๐Ÿ’ซ"), + ("Emily", "David", "Compatibility score: 5. There's potential โ€” stay open and see where it goes! ๐ŸŒˆ"), + ("Ethan", "Ava", "Compatibility score: 5. There's potential โ€” stay open and see where it goes! ๐ŸŒˆ"), + ("Benjamin", "Charlotte", "Compatibility score: 0. Perfect match โ€” pure harmony! ๐Ÿ’ž"), + ("Mia", "Lucas", "Compatibility score: 7. There's potential โ€” stay open and see where it goes! ๐ŸŒˆ") + ] + +# Test sanity check +def test_sanity_check(): + assert True + +# Test that output is a string +def test_format(sample_names): + for n1, n2, _ in sample_names: + output = compatibility_score.compatibility_score(n1, n2) + assert isinstance(output, str) + +# Test exact outputs for sample names +def test_exact_match(sample_names): + for n1, n2, expected in sample_names: + actual = compatibility_score.compatibility_score(n1, n2) + assert actual == expected, f"for ({n1},{n2}) expected {expected!r} but got {actual!r}" + +# Test that the compatibility score is same for same inputs +def test_consistent_output(sample_names): + for n1, n2, _ in sample_names: + a = compatibility_score.compatibility_score(n1, n2) + b = compatibility_score.compatibility_score(n1, n2) + assert a == b + +# Test that names with same letters but different cases produce the same score +def test_case_insensitive(): + pairs = [ + ("JOHN", "mary"), + ("John", "MARY"), + ("jOhN", "MaRy"), + ] + first = compatibility_score.compatibility_score(pairs[0][0], pairs[0][1]) + for n1, n2 in pairs[1:]: + result = compatibility_score.compatibility_score(n1, n2) + assert result == first, f"Case sensitivity affected score: {pairs[0]} vs ({n1},{n2})" + +# Test that names with spaces produce same score as trimmed names +def test_whitespace_handling(): + pairs = [ + (" Alice ", " Bob "), + ("Alice", "Bob"), + (" Alice", "Bob "), + ("\tAlice\t", "\nBob\n"), + ] + first = compatibility_score.compatibility_score(pairs[0][0], pairs[0][1]) + for n1, n2 in pairs[1:]: + result = compatibility_score.compatibility_score(n1, n2) + assert result == first, f"Whitespace affected score: {pairs[0]} vs ({n1},{n2})" + +# Test that order of names does not affect the score +def test_order(): + name_pairs = [ + ("Alice", "Bob"), + ("Emma", "Oliver"), + ("Luna", "Sol"), + ] + for n1, n2 in name_pairs: + forward = compatibility_score.compatibility_score(n1, n2) + reverse = compatibility_score.compatibility_score(n2, n1) + assert forward == reverse, f"Score differs when names swapped: {n1},{n2}" + +# Test that names with special characters are handled correctly +def test_special_characters(): + special_names = [ + ("John123", "Mary456"), + ("Anna-Maria", "Jean-Paul"), + ("O'Connor", "McDonald"), + (" Space ", " Tab\t"), + ] + for n1, n2 in special_names: + result = compatibility_score.compatibility_score(n1, n2) + assert isinstance(result, str), f"Failed to handle special characters in {n1},{n2}" + assert "Compatibility score:" in result + +# Test that compatibility score is always between 0 and 9 +def test_score_bounds(): + test_pairs = [ + ("aaaaaa", "eeeeee"), + ("xyz123", "wvt456"), + ("aeiou", "uoiea"), + ("A"*100, "E"*100), + ] + for n1, n2 in test_pairs: + result = compatibility_score.compatibility_score(n1, n2) + score = int(result.split(".")[0].split(": ")[1]) + assert 0 <= score <= 9, f"Score {score} out of bounds for {n1},{n2}" \ No newline at end of file diff --git a/tests/test_fortune.py b/tests/test_fortune.py new file mode 100644 index 0000000..f9e785a --- /dev/null +++ b/tests/test_fortune.py @@ -0,0 +1,58 @@ +import pytest +from src.fortuneluckpredictor import fortune + +def test_sanity_check(): + assert True + +def test_output_type(): + """Check that get_fortune returns a string.""" + result = fortune.get_fortune() + assert isinstance(result, str), f"Expected string, got {type(result)}" + + result_no_cookie = fortune.get_fortune(cookie=False) + assert isinstance(result_no_cookie, str), f"Expected string, got {type(result_no_cookie)}" + +def test_cookie_emoji(): + """Check that get_fortune(cookie=True) includes the cookie emoji.""" + result = fortune.get_fortune(cookie=True) + assert result.startswith("๐Ÿช"), f"Expected cookie emoji at start, got: {result}" + assert "๐Ÿช" in result, f"Cookie emoji not found in: {result}" + +def test_no_cookie_emoji(): + """Check that get_fortune(cookie=False) does not include the cookie emoji.""" + result = fortune.get_fortune(cookie=False) + assert not result.startswith("๐Ÿช"), f"Should not start with cookie emoji, got: {result}" + assert "๐Ÿช" not in result, f"Cookie emoji should not be in output: {result}" + +def test_valid_fortune(): + """Check that the fortune text (without emoji) is from the valid list.""" + valid_fortunes = [ + "Your resume will land on the perfect Meta recruiter's desk this week.", + "A Meta engineer will soon endorse your skills and open a new door.", + "The next coding interview you take will feel like designing the future of social connections.", + "Your grind on system design is about to pay off with a Reality Labs breakthrough.", + "A friendly referral will unlock the Meta pipeline you've been eyeing.", + "An unexpected DM from a Meta recruiter will kickstart your big tech journey.", + "Your portfolio will impress a Meta hiring manager far more than you expect.", + "A well-timed leetcode streak will boost your confidence for the Meta onsite.", + "Soon you'll celebrate a Meta offer with your closest supporters.", + "The next recruiter call you accept will change the trajectory of your career in the metaverse.", + "A spontaneous trip will lead to a story you retell for years.", + "Your curiosity will uncover a hobby that becomes a passion.", + "A long-standing question will be answered in the most unexpected way.", + "You will reconnect with someone who inspires you to dream bigger.", + "Small daily habits will compound into a breakthrough moment.", + "A random compliment will brighten your day and boost your confidence.", + "Your next challenge will reveal strength you didn't know you had.", + "A creative idea will blossom into a project that excites others.", + "Soon you will realize you are exactly where you need to be.", + "An opportunity to help someone will come, and your kindness will be remembered.", + ] + + result_with_cookie = fortune.get_fortune(cookie=True) + fortune_text = result_with_cookie.replace("๐Ÿช ", "").strip() + assert fortune_text in valid_fortunes, f"Fortune '{fortune_text}' not in valid list" + + result_no_cookie = fortune.get_fortune(cookie=False) + assert result_no_cookie in valid_fortunes, f"Fortune '{result_no_cookie}' not in valid list" + diff --git a/tests/test_futurePredictions.py b/tests/test_futurePredictions.py new file mode 100644 index 0000000..c13e50f --- /dev/null +++ b/tests/test_futurePredictions.py @@ -0,0 +1,90 @@ +import pytest +from src.fortuneluckpredictor import futurePredictions + +@pytest.fixture +def sample_names(): + return ["Alice", "Bob", "Charlie", "Diana", "Eve", ""] + +def test_sanity_check(): + assert True + +def test_output_type(sample_names): + """Check that futurePrediction returns a string.""" + for name in sample_names: + result = futurePredictions.futurePrediction(name) + assert isinstance(result, str), f"Expected string, got {type(result)}" + +def test_output_format(sample_names): + """Check that the output contains name, career, marriage age, kids info, and success level.""" + for name in sample_names: + result = futurePredictions.futurePrediction(name) + assert name in result or name == "", f"Name '{name}' not found in output: {result}" + assert "will be a" in result, f"'will be a' not found in output: {result}" + assert "get married at" in result, f"'get married at' not found in output: {result}" + assert "and you will be" in result, f"'and you will be' not found in output: {result}" + +def test_consistent_output(sample_names): + """Same name should always produce the same prediction.""" + for name in sample_names: + result1 = futurePredictions.futurePrediction(name) + result2 = futurePredictions.futurePrediction(name) + assert result1 == result2, f"Same name '{name}' produced different results: {result1} vs {result2}" + +def test_marriage_age_range(sample_names): + """Check that marriage age is between 25 and 39.""" + import re + for name in sample_names: + result = futurePredictions.futurePrediction(name) + # Extract the age number after "get married at" + match = re.search(r'get married at (\d+)', result) + assert match is not None, f"Could not find marriage age in: {result}" + age = int(match.group(1)) + assert 25 <= age <= 39, f"Marriage age {age} is not in valid range (25-39) in: {result}" + +def test_kids_count_range(sample_names): + """Check that kids count is between 0 and 3.""" + import re + for name in sample_names: + result = futurePredictions.futurePrediction(name) + if "with no kids" in result: + kids_count = 0 + else: + match = re.search(r'with (\d+) kid', result) + assert match is not None, f"Could not find kids count in: {result}" + kids_count = int(match.group(1)) + assert 0 <= kids_count <= 3, f"Kids count {kids_count} is not in valid range (0-3) in: {result}" + + +def test_career_in_list(sample_names): + """Check that the career is from the expected list of careers.""" + valid_careers = [ + "software engineer", "doctor", "lawyer", "teacher", "artist", + "entrepreneur", "scientist", "musician", "architect", "chef", + "engineer", "journalist", "psychologist", "designer", "consultant", + "nurse", "researcher", "manager", "analyst", "inventor" + ] + for name in sample_names: + result = futurePredictions.futurePrediction(name) + + import re + match = re.search(r'will be a (.+?) and get married', result) + assert match is not None, f"Could not find career in: {result}" + career = match.group(1).strip() + assert career in valid_careers, f"Career '{career}' not in valid list. Output: {result}" + +def test_success_level_in_list(sample_names): + """Check that the success level is from the expected list.""" + valid_success_levels = ["very successful", "successful", "somewhat successful", "moderately successful"] + for name in sample_names: + result = futurePredictions.futurePrediction(name) + import re + match = re.search(r'and you will be (.+)$', result) + assert match is not None, f"Could not find success level in: {result}" + success = match.group(1).strip() + assert success in valid_success_levels, f"Success level '{success}' not in valid list. Output: {result}" + +def test_different_names_different_predictions(): + """Different names should potentially produce different predictions.""" + results = [futurePredictions.futurePrediction(name) for name in ["Alice", "Bob", "Charlie", "Diana"]] + assert len(set(results)) >= 1, "All predictions were identical, which is unlikely" + diff --git a/tests/test_lucky_number.py b/tests/test_lucky_number.py new file mode 100644 index 0000000..355c0dc --- /dev/null +++ b/tests/test_lucky_number.py @@ -0,0 +1,66 @@ +import pytest +import math +from src.fortuneluckpredictor import luckyNumber + +@pytest.fixture +def sample_ranges(): + return [ + (1, 100), + (50, 60), + (100, 1), # reversed range + (7, 7), # single-number range + ] + +def test_sanity_check(): + assert True + +# Range check +def test_output_type_and_bounds(sample_ranges): + """Check that output is an integer within the expected bounds.""" + for r in sample_ranges: + result = luckyNumber.get_lucky_number(r) + assert isinstance(result, int) + a, b = r + lo, hi = (a, b) if a <= b else (b, a) + assert lo <= result <= hi, f"{result} not in {lo}-{hi}" + +# Returns same number when given same time +def test_consistent_same_seed(monkeypatch): + """When called within the same minute, output should be stable.""" + from datetime import datetime + class FixedTime(datetime): + @classmethod + def now(cls, tz=None): + return datetime(2025, 11, 3, 14, 45) + monkeypatch.setattr(luckyNumber, "datetime", FixedTime) + first = luckyNumber.get_lucky_number((1, 100)) + second = luckyNumber.get_lucky_number((1, 100)) + assert first == second + +# Returns different numbers at different times +def test_distribution_varies_over_time(monkeypatch): + """Different times should usually yield different lucky numbers.""" + from datetime import datetime + class TimeVariant(datetime): + @classmethod + def now(cls, tz=None): + # alternate between two moments + if hasattr(cls, "_toggle"): + del cls._toggle + return datetime(2025, 11, 3, 14, 46) + else: + cls._toggle = True + return datetime(2025, 11, 2, 14, 45) + monkeypatch.setattr(luckyNumber, "datetime", TimeVariant) + n1 = luckyNumber.get_lucky_number((1, 100)) + n2 = luckyNumber.get_lucky_number((1, 100)) + assert n1 != n2, f"Expected different results across distinct datetimes ({n1} == {n2})" + +def test_single_value_range(): + """If range has only one number, that number must be returned.""" + assert luckyNumber.get_lucky_number((42, 42)) == 42 + +def test_reversed_range_handled(): + """Swapped a,b should still produce valid result.""" + val = luckyNumber.get_lucky_number((100, 1)) + assert 1 <= val <= 100 diff --git a/tests/test_predict_day.py b/tests/test_predict_day.py new file mode 100644 index 0000000..aafec2c --- /dev/null +++ b/tests/test_predict_day.py @@ -0,0 +1,94 @@ +import pytest +from src.fortuneluckpredictor import predictDay + + +# --- Helpers --- + +def freeze_day(monkeypatch, year=2025, month=11, day=4): + #Freeze datetime.now() to a fixed date for deterministic tests. + from datetime import datetime as _DT + + class FixedDate(_DT): + @classmethod + def now(cls): + return cls(year, month, day) + + monkeypatch.setattr(predictDay,"datetime", FixedDate, raising=True) + + +def pin_fortunes(monkeypatch, items): + #Replace fortunes list inside predictDay. + monkeypatch.setattr(predictDay,"fortunes", items, raising=True) + + +# --- Tests --- + +def test_alias_normalization_is_case_and_space_insensitive(monkeypatch): + freeze_day(monkeypatch) + pin_fortunes(monkeypatch, ["ONLY_ONE"]) + for inp in ["fri", "Fri", " FRI "]: + out = predictDay.predict_day(inp) + assert out.startswith("Friday:") + assert "Fortune: ONLY_ONE" in out + + +def test_full_day_name(monkeypatch): + freeze_day(monkeypatch) + pin_fortunes(monkeypatch, ["X"]) + out = predictDay.predict_day("Monday") + assert out.startswith("Monday:") + assert "Fresh energy fills your week" in out + assert out.endswith("Fortune: X") + + +def test_blank_input_means_today(monkeypatch): + freeze_day(monkeypatch, 2025, 11, 5) # Wednesday + pin_fortunes(monkeypatch, ["ONLY_ONE"]) + out = predictDay.predict_day("") # blank โ†’ today + assert out.startswith("Wednesday:") + assert "Fortune: ONLY_ONE" in out + + +def test_whitespace_input(monkeypatch): + freeze_day(monkeypatch, 2025, 11, 6) # Thursday + pin_fortunes(monkeypatch, ["Y"]) + out = predictDay.predict_day(" ") + assert out.startswith("Thursday:") + assert out.endswith("Fortune: Y") + + +def test_invalid_day_raises(monkeypatch): + freeze_day(monkeypatch) + with pytest.raises(ValueError): + predictDay.predict_day("Funday") + + +def test_same_input_same_output_within_run(monkeypatch): + freeze_day(monkeypatch) + pin_fortunes(monkeypatch, ["A", "B", "C", "D", "E", "F", "G", "H"]) + a = predictDay.predict_day("Tuesday") + b = predictDay.predict_day("Tuesday") + assert a == b, "Same (date, day) should yield same fortune" + +def test_different_day_different_output(monkeypatch): + freeze_day(monkeypatch) + pin_fortunes(monkeypatch, ["A", "B", "C", "D", "E", "F", "G", "H"]) + + monday_output = predictDay.predict_day("Monday") + tuesday_output = predictDay.predict_day("Tuesday") + + # Since RNG seed depends on day name, outputs should differ + assert monday_output != tuesday_output, "Different days should yield different fortunes" + +@pytest.mark.parametrize("alias, full", [ + ("mon", "Monday"), ("tue", "Tuesday"), ("tues", "Tuesday"), + ("wed", "Wednesday"), ("thu", "Thursday"), ("thur", "Thursday"), + ("thurs", "Thursday"), ("fri", "Friday"), ("sat", "Saturday"), + ("sun", "Sunday"), +]) +def test_alias_table(alias, full, monkeypatch): + freeze_day(monkeypatch) + pin_fortunes(monkeypatch, ["ONLY_ONE"]) + out = predictDay.predict_day(alias) + assert out.startswith(f"{full}:") + assert "Fortune: ONLY_ONE" in out