{"id":51863271,"url":"https://github.com/ail-project/tempolocus","last_synced_at":"2026-07-24T11:30:41.487Z","repository":{"id":369922749,"uuid":"1292094485","full_name":"ail-project/tempolocus","owner":"ail-project","description":"Tempolocus is a time-series activity patterns and approximate location inference","archived":false,"fork":false,"pushed_at":"2026-07-07T13:58:05.000Z","size":126,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-07T14:05:29.073Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ail-project.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-07T09:40:17.000Z","updated_at":"2026-07-07T14:01:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ail-project/tempolocus","commit_stats":null,"previous_names":["ail-project/tempolocus"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/ail-project/tempolocus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ail-project%2Ftempolocus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ail-project%2Ftempolocus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ail-project%2Ftempolocus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ail-project%2Ftempolocus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ail-project","download_url":"https://codeload.github.com/ail-project/tempolocus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ail-project%2Ftempolocus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35841138,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-24T02:00:07.870Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-24T11:30:41.421Z","updated_at":"2026-07-24T11:30:41.472Z","avatar_url":"https://github.com/ail-project.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tempolocus\n\ntempolocus looks at time-series activity patterns to infer a location.\n\n## Using tempolocus \n\n`tempolocus` accepts two JSON shapes plus timestamp-list imports:\n\n- Weekly hourly buckets: a list of objects containing `day`, `hour`, and `count`.\n- Yearly daily buckets: an object containing `year`, `max`, and `nb`, where `nb`\n  is a list of `[YYYY-MM-DD, count]` pairs.\n- Timestamp lists: either a JSON list of UTC timestamp strings / Unix epoch\n  seconds, or a plain text file with one timestamp per line. This is useful for\n  PE `TimeDateStamp` / compiled-timestamp values and other event lists that\n  should be aggregated into weekly patterns.\n\nRun it from the repository:\n\n```bash\npython -m tempolocus samples/weekfull-chan1.json\npython -m tempolocus samples/year.json --format text\n```\n\nOr install the package locally:\n\n```bash\npython -m pip install -e .\ntempolocus samples/year-chan1.json --top 10\ntempolocus samples/year.json --holiday-profile public-worker --format text\ntempolocus samples/year.json --activity-signal peak --format text\ntempolocus timestamps.txt --kind timestamps --format text\n```\n\nTimestamp strings may use ISO-8601 forms such as `2026-01-05T09:15:00Z`\nor `2026-01-05 09:15:00 UTC`; numeric entries are interpreted as Unix epoch\nseconds in UTC. The output is probabilistic JSON and includes a generic `analysis.activity_type`\nclassification of `work-time`, `vacation-time`, or `mixed-time`. Weekly inputs\nrank timezone offsets, representative IANA zones, and a `probable_countries`\nlist that highlights countries whose multiple timezones appear in the top\ntimezone-offset results. Yearly inputs rank broad regions by comparing\nactivity on public-holiday calendars.\n\n### Supported yearly holiday regions\n\n| Area | Supported regions |\n| --- | --- |\n| 🌍 Africa | 🇩🇿 Algeria, 🇨🇮 Côte d'Ivoire, 🇪🇬 Egypt, 🇪🇹 Ethiopia, 🇬🇭 Ghana, 🇰🇪 Kenya, 🇲🇦 Morocco, 🇳🇬 Nigeria, 🇸🇳 Senegal, 🇿🇦 South Africa, 🇹🇳 Tunisia |\n| 🌏 Asia-Pacific | 🇦🇺 Australia, 🇨🇳 China, 🇮🇳 India, 🇯🇵 Japan, 🇲🇾 Malaysia, 🇳🇿 New Zealand, 🇰🇵 North Korea, 🇵🇭 Philippines, 🇸🇬 Singapore, 🇰🇷 South Korea, 🇹🇭 Thailand, 🇻🇳 Vietnam |\n| 🇪🇺 Europe | 🇦🇹 Austria, 🇧🇪 Belgium, 🇧🇦 Bosnia and Herzegovina, 🇧🇬 Bulgaria, 🇭🇷 Croatia, 🇨🇿 Czechia, 🇩🇰 Denmark, 🇪🇪 Estonia, 🇫🇮 Finland, 🇫🇷 France, 🇩🇪 Germany, 🇬🇷 Greece, 🇭🇺 Hungary, 🇮🇸 Iceland, 🇮🇪 Ireland, 🇮🇹 Italy, 🇱🇻 Latvia, 🇱🇹 Lithuania, 🇱🇺 Luxembourg, 🇳🇱 Netherlands, 🇳🇴 Norway, 🇵🇱 Poland, 🇵🇹 Portugal, 🇷🇴 Romania, 🇷🇺 Russia, 🇷🇸 Serbia, 🇸🇰 Slovakia, 🇸🇮 Slovenia, 🇪🇸 Spain, 🇸🇪 Sweden, 🇨🇭 Switzerland, 🇺🇦 Ukraine, 🇬🇧 United Kingdom |\n| 🌎 North America \u0026 Caribbean | 🇨🇦 Canada, 🇨🇷 Costa Rica, 🇨🇺 Cuba, 🇩🇴 Dominican Republic, 🇬🇹 Guatemala, 🇲🇽 Mexico, 🇵🇦 Panama, 🇺🇸 United States |\n| 🕌 Middle East | 🇧🇭 Bahrain, 🇮🇱 Israel, 🇯🇴 Jordan, 🇰🇼 Kuwait, 🇱🇧 Lebanon, 🇴🇲 Oman, 🇶🇦 Qatar, 🇸🇦 Saudi Arabia, 🇦🇪 United Arab Emirates |\n| 🌎 South America | 🇦🇷 Argentina, 🇧🇷 Brazil, 🇨🇱 Chile, 🇨🇴 Colombia, 🇵🇪 Peru, 🇺🇾 Uruguay |\n\nYearly analysis treats a lack of activity on holidays as the default signal;\npass `--activity-signal peak` when unusually high activity is the indicator you\nwant to match instead. Yearly analysis defaults to standard public holidays;\npass `--holiday-profile public-worker` to add public-sector worker references,\nsuch as state-worker, Golden Week, bridge-day, or administrative closure days,\nalongside standard holidays. The public-worker profile includes additional\nChina and Russia references for government and public-sector closure patterns,\nand South American public-servant references with common administrative bridge\nor year-end closure days.\n\nThe generic activity analysis compares weekly business-hours against\nweekend/off-hours activity, or yearly weekday activity against weekend activity.\nIt is intended as a broad activity-label heuristic rather than a declaration of\nwhy the activity occurred.\n\nThis is a heuristic first pass. Weekly data cannot uniquely identify an IANA\ntimezone without dates, and yearly data is sensitive to the meaning of the\nactivity counter.\n\n## Using tempolocus as a Python library\n\nInstall `tempolocus` in the Python environment that will import it:\n\n```bash\npython -m pip install tempolocus\n```\n\nFor development against a local checkout, install it in editable mode instead:\n\n```bash\npython -m pip install -e .\n```\n\nThe public package entry points are `detect`, `analyze_activity`, and\n`load_json`:\n\n```python\nfrom tempolocus import analyze_activity, detect, load_json\n\ndata = load_json(\"samples/weekfull-chan1.json\")\nresult = detect(data, top=10)\n\nprint(result[\"input_type\"])\nprint(result[\"results\"][0][\"label\"])\nprint(result[\"analysis\"][\"activity_type\"])\n\nactivity = analyze_activity(data)\nprint(activity[\"activity_type\"])\n```\n\n### `detect(data, kind=\"auto\", top=5, holiday_profile=\"standard\", activity_signal=\"lack\")`\n\nUse `detect` when you want the full inference result. It accepts already-loaded\nPython data structures rather than file paths, which makes it suitable for web\nservices, notebooks, pipelines, and tests. The return value is a dictionary with\nmetadata, assumptions, signal summaries, and ranked `results`.\n\nParameters:\n\n- `data`: one of the supported input shapes described below.\n- `kind`: `\"auto\"`, `\"weekly\"`, `\"yearly\"`, or `\"timestamps\"`. Use `\"auto\"`\n  when the input shape is unambiguous; force a kind when your caller already\n  knows what it provided.\n- `top`: number of ranked candidates to include. Must be at least `1`.\n- `holiday_profile`: for yearly inputs, `\"standard\"` or `\"public-worker\"`. The\n  public-worker profile adds public-sector closure references where available.\n- `activity_signal`: for yearly inputs, `\"lack\"` to match low activity on\n  holidays or `\"peak\"` to match unusually high activity on holidays.\n\nWeekly hourly bucket example:\n\n```python\nfrom tempolocus import detect\n\nweekly_rows = [\n    {\"day\": day, \"hour\": hour, \"count\": 10 if day \u003c= 4 and 9 \u003c= hour \u003c= 17 else 1}\n    for day in range(7)\n    for hour in range(24)\n]\n\nresult = detect(weekly_rows, kind=\"weekly\", top=3)\nfor candidate in result[\"results\"]:\n    print(candidate[\"probability\"], candidate[\"id\"], candidate[\"label\"])\n```\n\nTimestamp list example:\n\n```python\nfrom tempolocus import detect\n\ntimestamps = [\n    \"2026-01-05T09:15:00Z\",\n    \"2026-01-06 10:30:00 UTC\",\n    1767605400,  # Unix epoch seconds in UTC\n]\n\nresult = detect(timestamps, kind=\"timestamps\")\nprint(result[\"signals\"][\"timestamps_seen\"])\n```\n\nYearly daily bucket example:\n\n```python\nfrom tempolocus import detect\n\nyearly = {\n    \"year\": 2026,\n    \"max\": 42,\n    \"nb\": [\n        [\"2026-01-01\", 0],\n        [\"2026-01-02\", 18],\n        [\"2026-01-03\", 21],\n    ],\n}\n\nresult = detect(\n    yearly,\n    kind=\"yearly\",\n    top=5,\n    holiday_profile=\"public-worker\",\n    activity_signal=\"lack\",\n)\nprint(result[\"results\"][0][\"id\"], result[\"results\"][0][\"label\"])\n```\n\n### `analyze_activity(data, kind=\"auto\")`\n\nUse `analyze_activity` when you only need the generic activity classification\nwithout timezone or holiday-region rankings. It returns fields such as\n`activity_type`, `score`, and `shares`. Weekly inputs are classified from local\nbusiness-hours versus weekend/off-hours activity; yearly inputs compare weekday\nand weekend activity.\n\n```python\nfrom tempolocus import analyze_activity, load_json\n\nactivity = analyze_activity(load_json(\"samples/year.json\"), kind=\"yearly\")\nprint(activity[\"activity_type\"], activity[\"score\"])\n```\n\n### Input shape reference\n\n| Kind | Python shape | Notes |\n| --- | --- | --- |\n| `weekly` | `list[dict]` with `day`, `hour`, and `count` | `day` is `0` through `6`; `hour` is `0` through `23`; buckets are interpreted as UTC. |\n| `yearly` | `dict` with `year`, `max`, and `nb` | `nb` is a list of `[YYYY-MM-DD, count]` pairs. Missing days inside the observed range are filled as zero activity. |\n| `timestamps` | `list[str \\| int \\| float]` | Strings are parsed as UTC timestamps; numbers are Unix epoch seconds in UTC. |\n\nInvalid inputs raise `tempolocus.core.DetectionError`, a subclass of\n`ValueError`. Catch it around user-supplied data if you need to return a custom\nerror response:\n\n```python\nfrom tempolocus import detect\nfrom tempolocus.core import DetectionError\n\ntry:\n    result = detect(user_supplied_data)\nexcept DetectionError as error:\n    result = {\"error\": str(error)}\n```\n\n## Example \n\n~~~\nadulau@blakley:~/git/tempolocus$ python3 -m tempolocus samples/weekfull-chan1.json --format text  -n 10 --holiday-profile public-worker \ninput_type: weekly_timeseries\nconfidence: 0.220\nactivity_type: mixed-time (0.009)\nassumptions:\n  - Hourly buckets are interpreted as UTC; timezone candidates are offsets that make the activity look locally human.\n  - Weekly data cannot distinguish all IANA zones sharing the same offset, and daylight saving time is not inferable without dates.\nprobable_countries:\n  0.970  Russia (UTC+02:00, UTC+03:00, UTC+04:00, UTC+05:00, UTC+06:00, UTC+07:00, UTC+08:00)\n  0.009  France (UTC+01:00, UTC+03:00, UTC+04:00)\n  0.008  Kazakhstan (UTC+05:00, UTC+06:00)\n  0.003  United Kingdom (UTC+00:00, UTC+06:00)\n  0.002  Mongolia (UTC+07:00, UTC+08:00)\nresults:\n  0.208  timezone: UTC+05 Pakistan / western Central Asia\n          utc_quiet_window=19:00-01:00; local_quiet_window=00:00-06:00; quiet_activity_ratio=0.366; local_quiet_center=2.5\n  0.196  timezone: UTC+04 Gulf / Caucasus\n          utc_quiet_window=19:00-01:00; local_quiet_window=23:00-05:00; quiet_activity_ratio=0.366; local_quiet_center=1.5\n  0.143  timezone: UTC+06 Bangladesh / central Asia\n          utc_quiet_window=19:00-01:00; local_quiet_window=01:00-07:00; quiet_activity_ratio=0.366; local_quiet_center=3.5\n  0.129  timezone: UTC+03 East Africa / Arabia / Moscow\n          utc_quiet_window=19:00-01:00; local_quiet_window=22:00-04:00; quiet_activity_ratio=0.366; local_quiet_center=0.5\n  0.072  timezone: UTC+02 Eastern Europe / southern Africa\n          utc_quiet_window=19:00-01:00; local_quiet_window=21:00-03:00; quiet_activity_ratio=0.366; local_quiet_center=23.5\n  0.067  timezone: UTC+07 mainland Southeast Asia\n          utc_quiet_window=19:00-01:00; local_quiet_window=02:00-08:00; quiet_activity_ratio=0.366; local_quiet_center=4.5\n  0.039  timezone: UTC+01 Central Europe / West Africa\n          utc_quiet_window=19:00-01:00; local_quiet_window=20:00-02:00; quiet_activity_ratio=0.366; local_quiet_center=22.5\n  0.027  timezone: UTC+08 China / Singapore / Western Australia\n          utc_quiet_window=19:00-01:00; local_quiet_window=03:00-09:00; quiet_activity_ratio=0.366; local_quiet_center=5.5\n  0.023  timezone: UTC+00 Western Europe / West Africa\n          utc_quiet_window=19:00-01:00; local_quiet_window=19:00-01:00; quiet_activity_ratio=0.366; local_quiet_center=21.5\n  0.015  timezone: UTC-01 Azores / Cape Verde\n          utc_quiet_window=19:00-01:00; local_quiet_window=18:00-00:00; quiet_activity_ratio=0.366; local_quiet_center=20.5\n~~~\n\n## License\n\nThis project is licensed under the GNU Affero General Public License v3.0 or\nlater. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fail-project%2Ftempolocus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fail-project%2Ftempolocus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fail-project%2Ftempolocus/lists"}