Preface
In the previous post I talked about the importance of good design when making a published API. I ended by giving four ‘C’s of a good API: complete, compact, consistent and clear. Here I elaborate on them one by one.
Complete
An API design is complete if it allows customers to write client code that supports all use cases allowed by the business. This is a dynamic situation. With time the business may change their policy and allow new use cases against the same domain objects. Or it may introduce new domain object types. In either case a once complete API may suddenly become incomplete.
It would be a mistake to anticipate the business and to allow clients to do things that are not allowed by the business just in case it is allowed in the future. This type of thinking can lead to vulnerabilities and loopholes. In the worst case it can even lead to data corruption as business invariants may not be protected. For this reason it is best to start with an agreed set of business capabilities that can be developed into use cases and the API can be designed to support those.
Adding behavior or new types to a domain should not break existing client code. Breaking changes, though rare, can happen and the smart architect is prepared for these. This can be demonstrated with a concrete example.
Example. The Department of Motor Vehicles API may initially allow customers to manage their home address information with public APIs, but later decide that this business capability should no longer be supported. It would be much better for this to appear as a deprecation of an API rather than a reduction in the scope of a much broader API.
Now if the initial design had allowed the client code to supply a new customer info object (
UpdateDriverInfo
which included the customer’s address), this API would end up being clunky after the business change. The implementation would require new business logic (did the address change?) and failure modes. These changes would trigger changes to the documentation as well. While any breaking change is hard on client developers, adding failure modes to an existing API is worse since it entails extra work on the part of the client.A much better initial design would be an API that changed only the customer address (
UpdateDriverAddress
) as this could simply be deprecated in one release and eliminated in another. It would also be easier for client developers to see the effects of the deprecation.
You may be wondering if the logical continuation of this reasoning is to have separate APIs for the customer’s street address, city, zip code, etc. Your intuition will tell you that this is wrong, but there is also a solid principle against such granularity. Go back to the business and ask them: would you ever allow the customer to change their city without changing the rest of their address? They will tell you that the likelihood of a customer moving from one city to another but keeping the same street address is so small that it is effectively zero. Some things tend to change together and can be treated as a single “value”. This also leads to a more compact set of APIs, which leads to our second consideration for good API design.
Compact
There are two divergent schools of thought regarding the design of an API. A humane interface provides many convenience methods. It leads to shorter client code and a more fluid program structure. A minimal interface is just as complete but with a minimum of redundancy. Client code has one clear approach that tends to be longer.
When designing a published API, it is good to keep in mind that changes are very risky once the API is published. A client may be using the API and removing it may cause the client code to break. As a result, API methods are often deprecated and not removed (Java 9 removed APIs that had been deprecated since Java 2 and the repercussions will last almost as long). For this reason it is better to design a minimal interface and set a very high bar for adding convenience methods in future releases.
In reality risk mitigation through compactness is advice squarely aimed at framework and library API sets where business constraints are non-existent or negligible (the business has no business changing the definition of a natural logarithm). For business objects, whose API are primarily defined by business needs and must preserve business invariants, compactness is not as high a priority.
Instead business domain APIs should be first factored along the lines drawn by use cases which allow mutation. Business data that is needed to support command use cases should be encapsulated within a sub-domain. Then within that sub-domain compactness can play a secondary role in the design of the APIs.
Example. In the library checkout service the business may decide that a checkout is allowed only if
- the borrower has less than \$10.00 in fines
- the borrower has fewer than 25 items out already
- a copy of the item is not already being held by the borrower
An analysis of the use cases would show that the fines are primarily managed by another subdomain (library accounts). But the number and particulars of the held items belong to the same service that manages checkout. So for this reason a single API that returns the collection of all items held by a particular borrower would be both compact and appropriate. The same subdomain would also expose an API that allows a new item to be added the list (
BorrowItem
). Adding an item would be accomplished transactionally, so that two checkouts done at the same time from different clients would not both succeed if they would put that borrower over the 25 item limit or if they both attempted to add the same item.
Consistent
- Naming.
- Uniformity of behavior.
- Error handling.
- Event publishing.
- Documentation.
Clear (aka Intuitive)
Closely related to paper testing. Proportional to how close your solution is to the true model. Warning not to overestimate your own intuition (confirmation bias).
Conclusion
In the third post in this series I will delve into the design process, continuing to use the on-line library service example.


Sportsnet Fires Don Cherry Over Saturday's Comments
Cherry, who is widely known in Canada for his outspoken views, was dissatisfied with newcomers to the country who do not wear poppies - a symbol of respect for war veterans and military personnel in Canada.
Sportsnet announced the firing of its commentator Don Cherry on Monday, who has been hosting the Hockey Night in Canada show for over 33 years. The announcement followed criticism of Cherry's recent televised comments about immigrants not wearing poppies on 11 November, which the broadcaster found ''divisive'' and contrary to its values.
However, Sportsnet president Bart Yabsley paid tribute to Cherry's role in the game over the past four decades, calling him "synonymous with hockey".
Twitter users expressed frustration that Cherry was fired for his calls to honour fallen veterans while Canadian PM Justin Trudeau was re-elected in the recent national vote despite the ''blackface'' scandal.
Trudeau himself expressed support for critics of Cherry, as he retweeted posts slamming the commentator's recent comments about the poppies.
Earlier, the National Hockey League attacked Cherry, claiming that his comments were "offensive" and "contrary to the values" the NHL believes in.
On Saturday, Cherry said on CBC's Coach's Corner show that immigrants to Canada could ''at least'' get poppies on Remembrance Day to pay respect to Canadian war veterans.
"These guys paid for your way of life that you enjoy in Canada, these guys paid the biggest price", the commentator said.
Many social media users dismissed Cherry's comments as ''prejudiced'' and even ''racist'', calling for his resignation.
Don Cherry, 85, is known for his personality-packed straight-out comments, as well as flamboyant dressing style with fancy jackets and ties. In 2004, Cherry was named the all-time seventh-greatest Canadian in the relevant miniseries of the CBC broadcaster.


Toggle navigation
bitcontrol
Home
Channels
All Channels
Active Channels
Live Channels
Development
XML Control API
GitHub
Documentation
User Manual
SDK Manual
Refresh
Active


Returns a string that holds the players first and last name or ‘No player name found’, if we cannot find the player record.
# File app/helpers/search_helper.rb, line 36 def player_name(player_id) player = Player.where(player_id: player_id).first if player.present? str = "#{player.first_name} #{player.last_name}" else str = 'No player name found' end return str end
Returns an array of teams from the batting statistics data in a format that can be used by the select tag helper
# File app/helpers/search_helper.rb, line 20 def team_list teams = BattingStatistic.all.pluck(:team) teams = teams.uniq! team_array = [] if teams.present? teams.each do | team | team_array << [team, team] end end team_array.sort! return options_for_select(team_array) end
Returns an array that can be used in the select tag helper
# File app/helpers/search_helper.rb, line 4 def year_list years = [] start_year = Date.today.year # Build year list 200.times.each do |year| current_year = start_year - year years << ["#{current_year}", current_year] end return options_for_select(years) end


crownfund
About
Discover
Get Started
Mastercraft Bamboo Monitor Riser
A beautiful & handcrafted monitor stand to reduce neck and eye strain.
Back this project
Bookmark
$89,914
of $100,000 backed
5,007
total backers
56
days left
gdfvasdvfads


Join the Gabrielas and get to the 2048 BAFÃO!
New GameHow to play: Use your arrow keys to move the Gabrielas. When two Gabrielas with the same style touch, they merge into one!
Note: This site is the Gabriela version of 2048. All other apps or sites are derivatives or fakes, and should be used with caution.
Created by Matheus Schuh. Almost a style copy of Doge2048 by TinyMammals, based on 2048 by Gabriele Cirulli , 1024 by Veewo Studio and conceptually similar to Threes by Asher Vollmer.


fastify-example
Example webapp with Fastify
Setup and run
To complete project setup, do:
npm install
and run with
npm start
then point your browser to localhost:8000
Setup and run with Docker
Note that it's possible to let Docker do all inside a container, because all is described in a Dockerfile; see Dockerfile-usage for related commands.
Of course you need a local installation of Docker (recent, if possible latest), but nothing other.
Setup and run with Docker images at DockerHub
A simpler way to run the webapp is by using published images at DockerHub: smartiniatdocker09/fastify-example. Default images are based on default Node.js Docker image and contains development dependencies too (to reload content, run tests, etc), BASH shell, etc; there are even others:
- a variant one based on official Node.js Alpine Linux image, which is minimal (smaller, a lot) and is tailored for running in Production (dedicated user for running the webapp, no dev dependencies, no reload, etc)
- another based on official Distroless images for Node.js (from Google), small (but bigger than those based on Alpine Linux) and without a lot of stuff inside (there is no shell for example), so not so simple to debug but good for production environments Note that "normal" images are not so small (approx. 900 / 1000 MB) and could contain some vulnerability in some packages already installed in the OS, so pay attention if/when using them in production environments.
For example, you can run directly with:
docker run -d -p 8000:8000 -t smartiniatdocker09/fastify-example:latest-alpine
and browse to localhost:8000.
Published Docker tags are 'latest' / 'latest-alpine',
and starting from source code tag '2.2.0' there is:
the same tag, and even the '2.2.0-alpine' variant.
For more info look at tags - smartiniatdocker09/fastify-example.
Of course you need a local installation of Docker (recent, if possible latest), but nothing other.
Others
To run a development server (with hot reload enabled) instead execute this:
npm run start:dev
For other custom commands look the 'scripts' section inside 'package.json'.
Requirements
Fastify ^2.15.3, Node.js 10 LTS (10.13.0) or later.
Sources
Source code is all inside main repo: fastify-example.
Documentation generated from source code (API): here.
Note
Some features can be configured via environment variables;
if a .env
file is found in project root, all its contents
will be loaded into environmental variables.
Supported variables:
FASTIFY_OPTIONS
, set Fastify main options at startup, as a JSON string; to change logging level for example use something like: `{ "logger": { "level": "debug" } }' and to remove logging for example use: '{ }'HTTP_PORT
, set default HTTP port for the serverHTTP_ADDRESS
, set default HTTP address for the serverNATS_SERVER_URL
, set the URL for the NATS server (if enabled), or plugin defaultWEBHOOK_SECRET_KEY
, set the secret key to require by exposed webhookFEATURE_PLATFORM_INFO_DISABLE
, to disable the logging of some info related to the current runtime platform like: Node.js version, OS name, Fastify version, Webapp version, etc ...FEATURE_CHECK_RUNTIME_ENV_DISABLE
, to disable (not load) related plugin; if enabled and current Node.js environment is not compatible with the one set in 'package.json' by default it will throw an exception, so the webapp will crash (to avoid running the webapp in a not compliant env)FEATURE_FAVICON_DISABLE
, to disable (not load) related pluginFEATURE_WEBHOOK_DISABLE
, to disable (not load) related pluginFEATURE_HEALTHCHECK_DISABLE
, to disable (not load) related pluginFEATURE_CLOUDEVENTS_DISABLE
, to disable (not load) related plugin; note that this by default uses a public NATS server so disable it if not needed or if that server is not reachable for corporate firewall policiesFEATURE_CLOUDEVENTS_STRICT_DISABLE
, to disable strict mode in generated CloudEvents (if/when related plugin is enabled)FEATURE_CLOUDEVENTS_LOG_CONSOLE_DISABLE
, to disable CloudEvent serialization to consoleFEATURE_CLOUDEVENTS_LOG_FILE_DISABLE
, to disable CloudEvent serialization to console, by default true (so disabled); note that when enabled a new log file will be created at any run of the server, but previous (if present) will be overwritten, so any new run is fresh and old logs will be discardedFEATURE_NATS_DISABLE
, to disable (not load) related plugin if not specified default behavior will be applied.
As a sample, by default (unless disabled) some messages will be sent to a NATS queue, when the web application has started and when a client ask for a page. NATS server by default (in related plugin) is a public one, demo.nats.io.
License
Licensed under Apache-2.0.


We have 71 live insurers, on 5 products, each with business validation rules, that filter prospects based on their profile
Hierarchy of 492 legacy classes, no governance or auditability
Insurer exclusions based on object model (legacy code)
public void check(FieldContext context, FormuleMoto formule, Conducteur conducteur,
Vehicule vehicule, Void unused, Besoins besoins,
Set<EAbTestingScenario> scenarios)
throws ExclusionException {
if (besoins == null) {
return;
}
if (besoins.getDateDebutContrat() == null) {
return;
}
if (!DateHelper.isAfter(besoins.getDateDebutContrat(),
DateHelper.ajouteJoursADate(DateHelper.getToday(), NBR_JOURS),
DateHelper.EPrecision.jour)) {
throw new ExclusionException(DATE_EFFET_PLUS_60_JOURS);
}
}
Same rule, more fluent:
public ExclusionRule exclusionRule() {
return DOOV.when(dateContrat().after(todayPlusDays(60)))
.exclusionRule();
}
Java is verbose, but you can reduce the noise and write code like natural language with a fluent API
// JUnit API
assertEquals(9, fellowshipOfTheRing.size());
assertTrue(fellowshipOfTheRing.contains(frodo, sam));
assertFalse(fellowshipOfTheRing.contains(sauron));
// AssertJ API (fluent)
assertThat(fellowshipOfTheRing).hasSize(9)
.contains(frodo, sam)
.doesNotContain(sauron);
New elements in Java 8 makes it easier to write a fluent API
// java.util.function (io.doov.core.dsl.impl.LogicalBinaryCondition)
left.predicate().and(right.predicate()).test(model, context)
// java.util.stream (io.doov.core.dsl.impl.LogicalNaryCondition)
steps.stream().anyMatch(s -> s.predicate().test(model, context))
// lambda and method reference (io.doov.core.dsl.impl.NumericCondition)
predicate(greaterThanMetadata(field, value),
(model, context) -> Optional.ofNullable(value),
(l, r) -> greaterThanFunction().apply(l, r));
Many popular libraries propose fluent APIs like
jOOQ,
AssertJ,
Apache Spark, etc.
Dataset<Row> averagePrice = prices
.filter(value.<String>getAs("insurer")
.equals("COOL insurer"))
.groupBy("product")
.agg(avg("price").as("average"))
.orderBy(desc("average"));
"A domain-specific language (DSL) is a computer language specialized to a particular application domain. This is in contrast to a general-purpose language (GPL), which is broadly applicable across domains"
Domain Object Oriented Validation
dOOv is a fluent API for typesafe domain model validation
key model
// Root class of model
class Model {
User user;
}
// Add key named EMAIL
enum ModelFieldId {
EMAIL;
}
// Annotate email field
class User {
@Path(field = EMAIL
readable = ...)
String email;
}
code generate
// dOOv typed field class
class DslModel {
StringFieldInfo userEmail;
}
write rules
// Create rules by using
// generated fields
// in DslModel
DslModel
.when(userEmail.eq(...))
.validate()
// Optionaly add rules
// to a registry
.registerOn(DEFAULT);
get model
// Get model from somewhere
// or instanciate it
User user = new User();
user.setEmail("e@mail.com");
Model model = new Model();
model.setUser(user);
execute
// Use executeOn method
DslModel.when(email.matches(...))
.validate()
.executeOn(model);
// Or use the registry
DEFAULT.stream()
.map(rule -> rule.executeOn(model));
public static boolean validateAccount(User user,
Account account, Configuration config) {
if (config == null) {
return false;
}
if (user == null || user.getBirthDate() == null) {
return false;
}
if (account == null || account.getCountry() == null ||
account.getPhoneNumber() == null) {
return false;
}
if (YEARS.between(user.getBirthDate(), LocalDate.now()) >= 18
&& account.getEmail().length() <= config.getMaxEmailSize()
&& account.getCompany() == Company.LES_FURETS
&& account.getPhoneNumber().startsWith("+33")) {
return true;
}
return false;
}
public static SampleModel sample() {
User user = new User();
user.setId(1);
user.setFirstName("Foo");
user.setLastName("BAR");
// ...
Account account = new Account();
account.setCompany(Company.LES_FURETS);
account.setId(9);
// ...
public class Account extends Identity {
@SamplePath(field = SampleFieldId.LOGIN, readable = "account.login")
private String login;
// ...
@SamplePath(field = SampleFieldId.COMPANY, readable = "account.company")
private Company company;
// ...

SampleModelResourceBundle_fr_FR.properties
account.login = l'identifiant de connection
account.country = le pays
account.company = la soci\u00e9t\u00e9
...
> ./gradlew -p sample build
BUILD SUCCESSFUL in 23s
10 actionable tasks: 3 executed, 7 up-to-date
public class RulesOpenRndayTest {
SampleModelRule demoRule = DslSampleModel
.when(DOOV.matchAll(
userBirthdate.ageAt(today()).greaterOrEquals(18),
accountEmail.length().lesserOrEquals(configurationMaxEmailSize),
accountCompany.eq(Company.LES_FURETS),
accountPhoneNumber.startsWith("+33")))
.validate();
}
@Test
public void test_account() {
SampleModel sample = SampleModels.sample();
Result result = demoRule.executeOn(sample);
Assertions.assertThat(result).isTrue();
System.out.println(demoRule.readable());
System.out.println(demoRule.markdown(Locale.FRANCE));
}
rule when match all [user birthdate age at today >= 18, //
account email length is <= configuration max email size, //
account company = LES_FURETS, account phone number starts with '+33'] validate
* règle
* lorsque
* correspond à tous
* la date de naissance âge à la date du jour >= 18
* l'émail a une longueur <= la taille maximum de l'émail
* la société = LES_FURETS
* le numéro de téléphone commence par '+33'
* valider
@Test
public void test_account_failure_cause() {
SampleModel sample = SampleModels.sample();
sample.getAccount().setPhoneNumber("+1 12 34 56 78");
Result result = demoRule.executeOn(sample);
Assertions.assertThat(result).isTrue();
}
java.lang.AssertionError: Expected result to be true
(invalidated nodes: [account phone number starts with '+33'])
@Test
public void test_account_failure_cause() {
SampleModel sample = SampleModels.sample();
sample.getAccount().setPhoneNumber("+1 12 34 56 78");
Result result = demoRule.executeOn(sample);
Assertions.assertThat(result)
.isFalse()
.hasFailureCause("account phone number starts with '+33'");
}
@Test
public void test_account_failure_cause_2() {
SampleModel sample = SampleModels.sample();
sample.getAccount().setPhoneNumber("+1 12 34 56 78");
sample.getAccount().setCompany(Company.BLABLACAR);
Result result = demoRule.withShortCircuit(false).executeOn(sample);
Assertions.assertThat(result)
.isFalse()
.hasFailureCause("match all [account company = LES_FURETS, " +
"account phone number starts with '+33']");
}
Makes readable text generation possible:
we can output a multi-language rules catalog
in multiple formats (text, markdown, HTML, etc.)
We make daily statistics that helps us shape the business,
by removing or tweaking rules as needed
Bean Validation rules are not strongly typed since it's annotation based. This code will fail at compile time, but your IDE won't be able to tell you why.
public class Account {
@NotNull @Email
private Email email;
}
Because Bean Validation constraints are based on field annotation, cross validation between fields are only available through the extension mechanism.
public class Account {
@Pattern(regexp = "(FR)|(UK)")
private String country;
@Pattern(regexp = "???")
private String phoneNumber;
}
Bean Validation rules are not written with a natural language syntax and does not provide a syntax tree
@Size(min = 10, max = 200,
message = "About Me must be between 10 and 200 characters")
private String aboutMe;
userAboutMe().length().between(10, 200).validate().readable()
> When user about me length is between 10 and 200, validate
Visit our blog for details: https://beastie.lesfurets.com
We migrated our 492 business rules to dOOv,
we now have
compliance,
auditability,
governance,
clarity
(and more!)
Next step is extending the DSL to create an object to object mapping framework, Domain Object Oriented Mapping ( dOOm). It will feature the same AST to text and statistics functionnalities.
DOOV.mappings(
map(userEmail).to(insurerEmail),
map(userFirstName, userLastName)
.using(StringJoiner)
.to(insurerFullName),
when(userCountry.eq(FR))
.map(userPhone.to(insurerPhone))
)
Stay tuned for the next versions!
Validate that a profile
has an email with less than 20 characters
has at least 18 years when their country is France
their country is France when their phone number starts with '+33'
DOOV.when(accountEmail.length().lesserThan(20)
.and(userBirthdate.ageAt(today()).greaterThan(18)
.and(accountCountry.eq(Country.FR)))
.and(accountCountry.eq(Country.FR)
.and(accountPhoneNumber.startsWith("+33"))))
.validate();
model.getAccount().setEmail("test@test.org");
model.getAccount().setCountry(Country.FR);
model.getUser().setBirthDate(LocalDate.now().minusYears(19));
ValidationRule rule = DOOV
.when(accountEmail.length().lesserThan(20)
.and(userBirthdate.ageAt(today()).greaterThan(18)
.and(accountCountry.eq(Country.FR)))
.and(accountCountry.eq(Country.FR)
.and(accountPhoneNumber.startsWith("+33"))))
.validate();
Result result = rule.withShortCircuit(false)
.executeOn(wrapper);
System.out.println("> " + result.getFailureCause());
> account phone number starts with '+33'
Validate that a profile
has at least 18 years when their country is France
and their phone number starts with '+33'
has at least 21 years when their country is Canadian
and their phone number starts with '+1'
DOOV.when(userBirthdate.ageAt(today()).greaterThan(18)
.and(accountCountry.eq(Country.FR)
.and(accountPhoneNumber.startsWith("+33")))
.or(userBirthdate.ageAt(today()).greaterThan(21)
.and(accountCountry.eq(Country.CAN)
.and(accountPhoneNumber.startsWith("+1")))))
.validate();
model.getUser().setBirthDate(LocalDate.now().minusYears(22));
model.getAccount().setCountry(Country.FR);
ValidationRule rule = DOOV
.when(userBirthdate.ageAt(today()).greaterThan(18)
.and(accountCountry.eq(Country.FR)
.and(accountPhoneNumber.startsWith("+33")))
.or(userBirthdate.ageAt(today()).greaterThan(21)
.and(accountCountry.eq(Country.CAN)
.and(accountPhoneNumber.startsWith("+1")))))
.validate();
Result result = rule.withShortCircuit(false)
.executeOn(wrapper);
System.out.println("> " + result.getFailureCause());
> account phone number starts with '+33'
or (account country = CAN and
account phone number starts with '+1')
Validate that a profile
has at least 18 years when their country is France
and their phone number starts with '+33'
has at least 21 years when their country is Canadian
and their phone number starts with '+1'
DOOV.when(matchAny(
matchAll(userBirthdate.ageAt(today()).greaterThan(18),
accountCountry.eq(Country.FR),
accountPhoneNumber.startsWith("+33")),
matchAll(userBirthdate.ageAt(today()).greaterThan(21),
accountCountry.eq(Country.CAN),
accountPhoneNumber.startsWith("+1"))))
.validate();
model.getUser().setBirthDate(LocalDate.now().minusYears(22));
model.getAccount().setCountry(Country.FR);
ValidationRule rule = DOOV
.when(matchAny(
matchAll(userBirthdate.ageAt(today()).greaterThan(18),
accountCountry.eq(Country.FR),
accountPhoneNumber.startsWith("+33")),
matchAll(userBirthdate.ageAt(today()).greaterThan(21),
accountCountry.eq(Country.CAN),
accountPhoneNumber.startsWith("+1"))))
.validate();
Result result = rule.withShortCircuit(false)
.executeOn(wrapper);
System.out.println("> " + result.getFailureCause());
> match any [account phone number starts with '+33',
match all [account country = CAN,
account phone number starts with '+1']]
Validate that a profile country is Canadian or French
DOOV.when(accountCountry.anyMatch(Country.CAN, Country.FR))
.validate();
model.getAccount().setCountry(Country.UK);
ValidationRule rule = DOOV
.when(accountCountry.anyMatch(Country.CAN, Country.FR))
.validate();
Result result = rule.withShortCircuit(false)
.executeOn(wrapper);
System.out.println("> " + result.getFailureCause());
> account country != UK
Validate that a profile match at least two conditions :
DOOV.when(count(
userBirthdate.ageAt(today()).greaterThan(18),
accountCountry.eq(Country.FR),
accountPhoneNumber.startsWith("+33")).greaterThan(1))
.validate();
model.getUser().setBirthDate(LocalDate.now().minusYears(19));
model.getAccount().setCountry(Country.CAN);
model.getAccount().setPhoneNumber("1 23 45 67 89");
ValidationRule rule = DOOV
.when(count(
userBirthdate.ageAt(today()).greaterThan(18),
accountCountry.eq(Country.FR),
accountPhoneNumber.startsWith("+33"))
.greaterThan(1))
.validate();
Result result = rule.withShortCircuit(false)
.executeOn(wrapper);
System.out.println("> " + result.getFailureCause());
> account country = FR and account phone number starts with '+33'


The maps and data are courtesy of the Copernicus Climate Change service.
The data is freely available (with a registered account) through the Climata Data Store Toolbox. The Toolbox includes a (beta) Python API that lets you generate images, download files, and more with customized data processing.
The city names are courtesy of the GeoNames.org web site, a great resource for free geodata.
The tables are made possible by Derek Eder's awesome CSS to HTML converter.
A special thanks to all the scientists and developers whose hard work has made amazing tables like these available to the public!
Visit data site!


--- title: 'Academic Hiring in Ontario' author: 'Sinon Kiss' date: '2020-02-20' slug: [] categories: [] tags: ['university'] subtitle: '' summary: '' authors: [] lastmod: '2020-02-20T14:34:04-05:00' featured: no image: caption: '' focal_point: '' preview_only: no projects: [''] ---
Alex Usher has a series of recent blog posts here, here, here and here looking at academic versus non-academic expendituers and hiring in Canadian universities.
The basic thrust of Alex’s findings are that while the numbers of non-academic staff are increasing, their overall aggregate salaries are not. His conclusion is that academic salaries continue to rise partially because there is no churn in the academic job market as there is in non-academic jobs. Once you’re a prof, you’re a prof for a long time and your salary basically increases. In increases at faster rates when you are younger to be sure, but it basically increases the entire time.
I wanted to play with the data from the Ontario public sector salary disclosure list (‘’Sunshine List’’) to see if we could learn anything about it.
If you don’t know already the Sunshine List is a dataset published annually by the Government of Ontario. It contains the name, title and salary of each public sector employee (broadly construed) who earned more than $100,00 in any calendar year. It was legislated in 1996 at the height of the Common Sense Revolution. The most generous interpretation of the legislation is that it is a tool for citizens to hold public employees to account. A different interpretation (which I favour) is that it was an attempt to shame, embarass public sector employees and to inflame public opinion as a resource to control public sector salaries.
When the legislation was passed, however, there were no provisions for adjusting the salary level upwards with inflation. That was over 20 years ago and so, inexorably, the sunshine list has gotten larger and larger. You would be shocked at the number of front-line nurses and police officers on that list now.
So, as time passes, the sunshine list will become an increasingly useful data source to track, in a very fine-grained way, salary patterns in Ontario’s public sector. The median salary for full-time university faculty in 2017-2017 was $128,950, meaning that the Ontario sunshine list contains more than half of Ontario full-time professors. Almost inexorably, that will include everyone one day.
I wasn’t able to locate data on the average salary for non-academic staff in Ontario and it is almost certainly lower, but, again, given inflation, it won’t be an insignificant portion of the university workforce. Just remember there are a lot of teachers, nurses and police constables on that list, so university professionals like (well, nurses, engineering technicians in the physical plants, human resource executives, PR people) might plausibly be in the ball park.
And, at the very least, this exercise might be fun.
So, the headline finding from Alex’s numbers are that while universities are hiring more non-academic staff than academic staff, the overall share of the compensation budget for non-academic staff is growing at about the same rate as for academic staff. His two charts here show this:
The first thing we can check is Alex’s finding that academic staff hiring is growing more slowly than non-academic staff. In order to do that, we need to take the myriad job titles in the sunshine list and code them as either academic or non-academic. The code below shows what I’ve done.1
Basically, the lines below take any job title that contains the first string pattern in the line and sets it to be either academic or non-academic. As a rule of thumb, if a job title contained the titles ‘prof’, ‘lecturer’, ‘dean’, ‘chair’, ‘course director’, anything that is really focussed on doing teaching and research, then it got classed as academic. There were some edge cases, that were presumably attached mostly to university teaching hospitals (e.g. clinical psychologist). I set those to be missing, to exclude them. And the rest, I set to be non-academic.
salaries %>%
mutate(Category=case_when(
#These categorize these job titles into the academic category
str_detect(`Job Title`, 'Professor') ~"Academic",
str_detect(`Job Title`, 'Prof.') ~"Academic",
str_detect(`Job Title`, 'Canada Research Chair') ~"Academic",
str_detect(`Job Title`, 'Lecturer') ~"Academic",
str_detect(`Job Title`, 'Faculty') ~"Academic",
str_detect(`Job Title`, "Librar") ~"Academic",
str_detect(`Job Title`, 'Course Director') ~"Academic",
str_detect(`Job Title`, 'Chair') ~"Academic",
str_detect(`Job Title`, 'Dean') ~"Academic",
str_detect(`Job Title`, 'Principal') ~"Academic",
str_detect(`Job Title`, 'Instructor') ~"Academic",
str_detect(`Job Title`, 'Research Associate') ~"Academic",
str_detect(`Job Title`, 'Department Head')~ "Academic",
str_detect(`Job Title`, 'Research Director')~ "Academic",
str_detect(`Job Title`, 'Research Manager')~ "Academic",
str_detect(`Job Title`, 'Queen\'s National Scholar')~ "Academic",
str_detect(`Job Title`, 'Molecular Biologist')~ "Academic",
str_detect(`Job Title`, 'Senior Researcher')~ "Academic",
str_detect(`Job Title`, 'Contract Academic Staff')~ "Academic",
str_detect(`Job Title`, 'Manager, Analytical Biology')~ "Academic",
str_detect(`Job Title`, 'Director, Sudbury Neutrino')~ "Academic",
str_detect(`Job Title`, 'Director Wilfrid Laurier Press')~ "Academic",
str_detect(`Job Title`, 'Tutor')~ "Academic",
#These are grey area jobs that are likely associated with university teaching hospitals; they are categorized as missing.
str_detect(`Job Title`, 'Psychologist')~ NA_character_,
str_detect(`Job Title`, 'Clinician')~ NA_character_,
str_detect(`Job Title`, 'Clinical Research Coordinator')~ NA_character_,
str_detect(`Job Title`, 'Physician') ~NA_character_,
#If a job title is anything other than the above, it is classed as non-academnic
TRUE ~ "Non-Academic"
)
) %>%
filter(Sector=="Universities")->salaries2
The first thing to do is see how many non-academic and academics are showing up, over time, on the sunshine list.
Is Alex’s pattern that there is greater hiring for non-academic than for academic staff showing up for people earning $100,000? The answer is that it kind of looks like it. this graph shows the percent change over the previous year in the number of people in the non-academic and academic categories earning $100,00 per year in Ontario.
It looks like there was about rough parity in the late 1990s and early 2000s and then non-academic staff have grown more rapidly than academic staff. Now, this doesn’t particularly tell us if this is actually more bodies, it tells us there are increasingly more non-academic people on the sunshin list than academic staff.
But, we can also check whether there are different compensation patterns for non-academic staff and academic staff. Here’s the average salary over time for the two categories.
Now that is a very different pattern. Average non-academic salaries (of people on the sunshine list) were significantly higher than academic faculty in the mid-1990s, but there has been a complete switch since then.
I think this is a super interesting pattern and I wonder if there is kind of a thermostatic reaction going on where senior non-academic and academic staff in universities watch each other and struggle for pay increases. In fact, I wonder if one perverse effect of the sunshine list was to increase academic salaries because they suddenly had very clear access to data on their non-academic counterparts who had a series of good years in the mid- to late-1990s.
I think these data are basically compatible with Alex’ conclusions which is that non-academic hiring has been growing at lower salaries than non-academic hiring. But the only way to square the first graph with the second is that any growth in Ontario in the last 20 years either in the # of non-academic staff or their salaries is constrained to people earning maybe just above the $100,00 salary mark. Otherwise, we would have seen an increase in the average salary of people on the sunshine list; but we haven’t.
For academic staff, though, while the # of people showing up on the sunshine list has been slower (growing presumably because of a combination of inflationary pressures on salaries overall and on some hiring expansion), the average salary above $100,000 has shown absolutely no sign of slowing down. I think this is basically because the academic career path, as currently structured in Ontario, guarantees salary increases at all ranks on the job (early and late).
And I wonder what this will look like going forward. One of the major differences between the non-academic and academic staff sectors (at the high levels) are that the former are not predominantly unionized, while the latter are. The former’s salaries are primarily dictated by market forces; how will that work in a sunshine list environment?
As inflation drives more and more people onto the sunshine list, more and more non-academic unionized employees will be on that list, eliminating that difference. That will take some time, but, barring changes in the legislation, it will happen. Will that make the thermostatic see-saw worse?
Ontario’s public sector salary disclosure list offers a really interesting dataset on public sector salaries, and it’s going to become even more important as inflation drives more and more people onto the list. Second, Alex Usher’s finding that aggregate numbers of non-academic staff are growing at greater rates in the university sector than academic rates is compatible with these data. But we these data don’t tell us if this is because there are increased #s of non-academic people or just more and more people earning over $100,000. However, taking into account the fact that the average of non-academic staff salaries is flatlined with Alex’s finding, it suggests that universities are hiring more and more non-academic people around the $100,00 mark. Third, the average salaries of academic staff earning over $100,000 are are growing at much faster rate than their non-academic colleagues.
Just as a check that my method of selecting job titles into the academic or non-academic category was plausible. Here are the top 30 uniquely occuring job titles in each category.
| Job Title | Category | n |
|---|---|---|
| Associate Professor | Academic | 39944 |
| Professor | Academic | 35672 |
| Assistant Professor | Academic | 9572 |
| Faculty Member | Academic | 7952 |
| Faculty | Academic | 4327 |
| Full Professor | Academic | 3965 |
| Professeur(e) agrégé(e) / Associate Professor | Academic | 2189 |
| Professeur(e) titulaire / Full Professor | Academic | 1969 |
| Lecturer | Academic | 1667 |
| Professor/Professeur | Academic | 1286 |
| Professor of Education | Academic | 1058 |
| Professeur/Professor | Academic | 823 |
| Professeur(e) adjoint(e) / Assistant Professor | Academic | 808 |
| Professeur - Professor | Academic | 692 |
| Instructor | Academic | 642 |
| Senior Lecturer | Academic | 614 |
| Professor of Dentistry | Academic | 556 |
| Professor of English | Academic | 555 |
| Professor of Economics | Academic | 554 |
| Librarian | Academic | 543 |
| Director | Non-Academic | 555 |
| Gestionnaire / Manager | Non-Academic | 253 |
| President | Non-Academic | 253 |
| Directeur(trice) / Director | Non-Academic | 221 |
| Executive Officer | Non-Academic | 180 |
| Registrar | Non-Academic | 139 |
| Directeur(rice) / Director | Non-Academic | 129 |
| Executive Director | Non-Academic | 111 |
| Information Technology Analyst | Non-Academic | 108 |
| Chief Administrative Officer | Non-Academic | 107 |
| Manager | Non-Academic | 105 |
| Chief Information Officer | Non-Academic | 103 |
| Associate Director | Non-Academic | 99 |
| Controller | Non-Academic | 86 |
| University Registrar | Non-Academic | 82 |
| Business Manager | Non-Academic | 78 |
| Counsellor | Non-Academic | 78 |
| Lead Distributed Systems Specialist | Non-Academic | 78 |
| University Secretary | Non-Academic | 78 |
| Directeur(rice) adjoint(e) / Assistant Director | Non-Academic | 76 |


FOSSGIS2010 - 22
FOSSGIS 2010
Freie und Open Source Software für Geoinformationssysteme
| Speakers | |
|---|---|
| Astrid Emde |
| Schedule | |
|---|---|
| Day | Freitag - 2010-03-05 |
| Room | Workshop 2 (Geb. SI, SI0202) |
| Start time | 09:00 |
| Duration | 08:00 |
| Info | |
| ID | 156 |
| Event type | Developer Meeting |
| Track | Developer-Treffen |
Mapbender Developer-Sprint
The fourth day of the FOSSGIS 2010 conference in Osnabrück, Germany (Friday 5th of March) saw a day of Mapbender hacking. A dozen developers and power users flocked around Mapbender to discuss general issues and hack a bit. The agenda (link) listed several points we wanted to address explicitly.
One important question was the next release. The last year has seen massive refactoring and a lot of new functionality and finally Mapbender ships with an install script. But a lot of old modules, especially quick & dirty hacks broke during the refactoring. The more generically focused developers favor a clear cut and to remove old stuff. The more user-implementation focused developers favor a slower approach and keeping the broad set of functionality, even if it means to carry along old stuff. Many Mapbender installations are long lived and typically host metadata for hundreds and thousands of services and users who need the web mapping platform in every day work. This puts a lot of responsibility on the development group to create stable software and smooth upgrade paths. Therefore it was decided to release a bug fixed version 2.6.1 next week.
One important goal of latest development has been the leverage of jQuery and jQuery UI to pimp the Mapbender front end. Adopting jQuery and being able to include tons of PlugIns make Mapbender look and feel a lot more up-to-date and addresses the user's desire for sleeker user interfaces. Another very important improvement that came out of the refactoring efforts is much cleaner code which will make it a lot easier for new developers to get into Mapbender.
Therfore it was decided to step up a full version number in summer with new features and most importantly a clean and well documented API. A full version step also justifies that some older installations will brake at some points. This is an invigorating decision as it will allow us to be cleaner and meaner with old stuff. The new Mapbender API will make developing much easier and more fun in.
The first release candidate 3.0 will come out after the Bolsena hack sprint. It will include a thorough integration of OpenLayers, a completely refactored user interface and potentially even a connection to a metadata bucket with thousands of OGC services to choose from. But that is another story.
Have fun.
Read more about the Sprint at:
- https://www.mapbender.org/2010-03-05MapbenderDevelopment_Sprint
- Blog: http://www.osgeo.org/node/1009
| Vorträge | Vorträge (Meist 20 Minuten plus Fragen+Antworten) |
| OSM-Vorträge | Vorträge im OSM-Teil (Meist 20 Minuten plus Fragen+Antworten) |
| Workshops | Kostenpflichtige Workshops am Rechner (90 Minuten) |
| Community Sessions | Moderierte Vortrags- und Diskussion-Veranstaltung (60-90 Minuten) |
| Anwendertreffen | Treffen für Anwender bestimmter Software |
| Developer-Treffen | Treffen für Entwickler bestimmter Software |


--- layout: page status: publish published: true title: Glossary categories: [] tags: [] comments: [] ---


{% extends "base.html" %} {% block extra_head %} {% endblock %} {% block content %}
Easily launch CloudMan, CloudBioLinux and Galaxy platforms on Cloud Computing resources (including Amazon Web Services).
Hide advanced options Show advanced startup options


Three years ago I wrote an article about webockets. In fact I’ve written several articles about Websockets (Websockets and real time communications is something that I’m really passionate about), but today I would like to pick up this article. Nowadays I’m involved with several Django projects so I want to create a similar working prototype with Django. Let’s start:
In the past I normally worked with libraries such as socket.io to ensure browser compatibility with Websockets. Nowadays, at least in my world, we can assume that our users are using a modern browser with websocket support, so we’re going to use plain Websockets instead external libraries. Django has a great support to Websockets called Django Channels. It allows us to to handle Websockets (and other async protocols) thanks to Python’s ASGI’s specification. In fact is pretty straightforward to build applications with real time communication and with shared authentication (something that I have done in the past with a lot of effort. I’m getting old and now I like simple things :))
The application that I want to build is the following one: One Web application that shows the current time with seconds. Ok it’s very simple to do it with a couple of javascript lines but this time I want to create a worker that emits an event via Websockets with the current time. My web application will show that real time update. This kind of architecture always have the same problem: The initial state. In this example we can ignore it. When the user opens the browser it must show the current time. As I said before in this example we can ignore this situation. We can wait until the next event to update the initial blank information but if the event arrives each 10 seconds our user will have a blank screen until the next event arrives. In our example we’re going to take into account this situation. Each time our user connects to the Websocket it will ask to the server for the initial state.
Our initial state route will return the current time (using Redis). We can authorize our route using the standard Django’s protected routes
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from ws.redis import redis
@login_required
def initial_state(request):
return JsonResponse({'current': redis.get('time')})
We need to configure our channels and define a our event:
from django.urls import re_path
from ws import consumers
websocket_urlpatterns = [
re_path(r'time/tic/$', consumers.WsConsumer),
]
As we can see here we can reuse the authentication middleware in channel’s consumers also.
import json
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class WsConsumer(AsyncWebsocketConsumer):
GROUP = 'time'
async def connect(self):
if self.scope["user"].is_anonymous:
await self.close()
else:
await self.channel_layer.group_add(
self.GROUP,
self.channel_name
)
await self.accept()
async def tic_message(self, event):
if not self.scope["user"].is_anonymous:
message = event['message']
await self.send(text_data=json.dumps({
'message': message
}))
We’re going to need a worker that each second triggers the current time (to avoid problems we’re going to trigger our event each 0.5 seconds). To perform those kind of actions Django has a great tool called Celery. We can create workers and scheduled task with Celery (exactly what we need in our example). To avoid the “initial state” situation our worker will persists the initial state into a Redis storage
app = Celery('config')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
sender.add_periodic_task(0.5, ws_beat.s(), name='beat every 0.5 seconds')
@app.task
def ws_beat(group=WsConsumer.GROUP, event='tic_message'):
current_time = time.strftime('%X')
redis.set('time', current_time)
message = {'time': current_time}
channel_layer = channels.layers.get_channel_layer()
async_to_sync(channel_layer.group_send)(group, {'type': event, 'message': message})
Finally we need a javascript client to consume our Websockets
let getWsUri = () => {
return window.location.protocol === "https:" ? "wss" : "ws" +
'://' + window.location.host +
"/time/tic/"
}
let render = value => {
document.querySelector('#display').innerHTML = value
}
let ws = new ReconnectingWebSocket(getWsUri())
ws.onmessage = e => {
const data = JSON.parse(e.data);
render(data.message.time)
}
ws.onopen = async () => {
let response = await axios.get("/api/initial_state")
render(response.data.current)
}
Basically that’s the source code (plus Django the stuff).
Application architecture The architecture of the application is the following one:
Frontend The Django application. We can run this application in development with python manage.py runserver
And in production using a asgi server (uvicorn in this case)
uvicorn config.asgi:application --port 8000 --host <IP_ADDRESS> --workers 1
In development mode:
celery -A ws worker -l debug
And in production
celery -A ws worker --uid=nobody --gid=nogroup
We need this scheduler to emit our event (each 0.5 seconds)
celery -A ws beat
Message Server for Celery In this case we’re going to use Redis
Docker With this application we can use the same dockerfile for frontend, worker and scheduler using different entrypoints
Dockerfile:
FROM python:3.8
ENV TZ 'Europe/Madrid'
RUN echo $TZ > /etc/timezone && \
apt-get update && apt-get install -y tzdata && \
rm /etc/localtime && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata && \
apt-get clean
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ADD . /src
WORKDIR /src
RUN pip install -r requirements.txt
RUN mkdir -p /var/run/celery /var/log/celery
RUN chown -R nobody:nogroup /var/run/celery /var/log/celery
And our whole application within a docker-compose file
version: '3.4'
services:
redis:
image: redis
web:
image: clock:latest
command: /bin/bash ./docker-entrypoint.sh
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
depends_on:
- "redis"
ports:
- 8000:8000
environment:
ENVIRONMENT: prod
REDIS_HOST: redis
celery:
image: clock:latest
command: celery -A ws worker --uid=nobody --gid=nogroup
depends_on:
- "redis"
environment:
ENVIRONMENT: prod
REDIS_HOST: redis
cron:
image: clock:latest
command: celery -A ws beat
depends_on:
- "redis"
environment:
ENVIRONMENT: prod
REDIS_HOST: redis
If we want to deploy our application in a K8s cluster we need to migrate our docker-compose file into a k8s yaml files. I assume that we’ve deployed our docker containers into a container registry (such as ECR)
Frontend:
apiVersion: apps/v1
kind: Deployment
metadata:
name: clock-web-api
spec:
replicas: 1
selector:
matchLabels:
app: clock-web-api
project: clock
template:
metadata:
labels:
app: clock-web-api
project: clock
spec:
containers:
- name: web-api
image: my-ecr-path/clock:latest
args: ["uvicorn", "config.asgi:application", "--port", "8000", "--host", "<IP_ADDRESS>", "--workers", "1"]
ports:
- containerPort: 8000
env:
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
name: clock-app-config
key: redis.host
---
apiVersion: v1
kind: Service
metadata:
name: clock-web-api
spec:
type: LoadBalancer
selector:
app: clock-web-api
project: clock
ports:
- protocol: TCP
port: 8000 # port exposed internally in the cluster
targetPort: 8000 # the container port to send requests to
Celery worker
apiVersion: apps/v1
kind: Deployment
metadata:
name: clock-web-api
spec:
replicas: 1
selector:
matchLabels:
app: clock-web-api
project: clock
template:
metadata:
labels:
app: clock-web-api
project: clock
spec:
containers:
- name: web-api
image: my-ecr-path/clock:latest
args: ["uvicorn", "config.asgi:application", "--port", "8000", "--host", "<IP_ADDRESS>", "--workers", "1"]
ports:
- containerPort: 8000
env:
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
name: clock-app-config
key: redis.host
---
apiVersion: v1
kind: Service
metadata:
name: clock-web-api
spec:
type: LoadBalancer
selector:
app: clock-web-api
project: clock
ports:
- protocol: TCP
port: 8000 # port exposed internally in the cluster
targetPort: 8000 # the container port to send requests to
Celery scheduler
apiVersion: apps/v1
kind: Deployment
metadata:
name: clock-cron
spec:
replicas: 1
selector:
matchLabels:
app: clock-cron
project: clock
template:
metadata:
labels:
app: clock-cron
project: clock
spec:
containers:
- name: clock-cron
image: my-ecr-path/clock:latest
args: ["celery", "-A", "ws", "beat"]
env:
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
name: clock-app-config
key: redis.host
Redis
apiVersion: apps/v1
kind: Deployment
metadata:
name: clock-redis
spec:
replicas: 1
selector:
matchLabels:
app: clock-redis
project: clock
template:
metadata:
labels:
app: clock-redis
project: clock
spec:
containers:
- name: clock-redis
image: redis
ports:
- containerPort: 6379
name: clock-redis
---
apiVersion: v1
kind: Service
metadata:
name: clock-redis
spec:
type: ClusterIP
ports:
- port: 6379
targetPort: 6379
selector:
app: clock-redis
Shared configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: clock-app-config
data:
redis.host: "clock-redis"
We can deploy or application to our k8s cluster
kubectl apply -f .k8s/
And see it running inside the cluster locally with a port forward
kubectl port-forward deployment/clock-web-api 8000:8000
And that’s all. Our Django application with Websockets using Django Channels up and running with docker and also using k8s.
Source code in my github


Nicholas Beekhuizen
Experience
Bridge Investment Group
IT Project Manager
June 2019 - present
-
Complete strategic analysis and remediation of IT infrastructure
projects across multiple verticals with $20B in assets under
management
-
Serve as liaison for all IT and technology-services vendors or
providers for 160+ properties and five corporate offices
throughout the U.S.
-
Revamped Agile intake processes, eliminating 25% of wasted
development hours and shortening deployment timelines to ensure
critical product delivery dates were met
AbleVets
Cyber Threat Intelligence Anlayst
Sep 2018 - June 2019
-
Authored and disseminated serialized reporting and weekly
situation briefings to thousands of Department of Veterans
Affairs personnel
-
Performed analytic support focused on cyber doctrine and
policies, and threat actor capabilities and intent
-
Provided targeted attack detection and analysis, including
development of custom signatures and analytics for Advanced
Persistant Threat groups
Red Touch Media/Laduma
Project Manager, Production ManagerJune 2014 - Sep 2018
-
Directed the execution and development of 200+ custom software
solutions and 100+ virtual, augmented, and mixed reality
projects by dual-continent teams of over 20 software engineers,
producers, editors, and VFX artists
-
Planned and executed virtual reality location shoots in Morocco,
Namibia, and South Africa; France, the U.K., and Scotland; both
eastern and western contiguous U.S., Hawaii, and Mexico
United States Air Force
Intelligence AnalystOct 2006 - Aug 2013
-
Achieved the rank of Staff Sergeant while serving as an
Operations Intelligence Analyst for nearly seven years
-
Decorated for meritorious service with the Bronze Star Medal,
Joint Service Commendation and Achievement Medals, and Air Force
Commendation Medal, along with numerous quarterly and yearly
awards for leadership and volunteering
-
Managed multiple teams of analysts at each level of the
Intelligence Community, including developing robust training
curriculum for junior analysts including research methods,
executive-level presentation, and public speaking
Education
University of Utah
Bachelor of Science, Political ScienceMay 2017
Community College of the Air Force
Associate of Applied Science, Intelligence StudiesAug 2012


[§342G-31 Guidelines.] The process of county and state integrated solid waste management planning shall be expedited to the greatest degree possible. The department, within six months of July1, 1991, shall issue guidelines for the development of county plans. The draft guidelines shall be circulated to the counties for review and comment not less than forty-five days before their issuance. [L 1991, c 324, pt of §2]
Revision Note
"July1, 1991" substituted for "the effective date of this chapter".


{% extends 'base/base.html' %} {% load i18n %} {% load widget_tweaks %} {% block bodyblock %}
To Reset your password please provide the Email address that is associated with the account
{{ error }}


.
The Akan people of Ghana frequently name their children after the day of the week they were born and the order in which they were born. These "day names" have further meanings concerning the soul and character of the person. Middle names have considerably more variety and can refer to their birth order, twin status, or an ancestor's middle name.
Akan names are derived from Ghanian culture. Frequently in Ghana, children are given their first name as
a 'day name'
which corresponds to the day in the week they were born. Here are Ghanian day names.
Male
Sunday: Kwasi
Monday: Kwadwo
Tuesday: Kwabena
Wednesday: Kwaku
Thursday: Yaw
Friday: Kofi
Saturday: Kwame
Female
Sunday: Akosua
Monday: Adwoa
Tuesday: Abenaa
Wednesday: Akua
Thursday: Yaa
Friday: Afua
Saturday: Ama
Most people do know when their birthdays are, but some might not know what day of the week they were
born.
This is a web application that takes a user's birthday
and calculates the day of the week they were
born and then depending on their gender it will output their Akan Name.Fill the form correctly and get your akan name;
Your Akan Name Is;
For more info:


CheckMyPR
Ever wondered what happened to that pull request you opened up years ago but never got merged? Now you can check. Just login to Github to see the status of all your open pull requests.


Crucifix Choke
Red Book Pg 90, Opponent is in turtle, your on the left side of them,
Step inbetween opponents arms with left leg trapping their left arm behind your left knee, cross your feet as per triangle,
reach over with your right arm and underhook ops right arm, place your head on the floor, on right side of opponent.


Contents:
This section shows how to use mgs, from basic to advanced use-cases.
Important
Every code snippet will use base64 to demonstrate common codec functionalities.
Unless otherwise specified, everything can be used with other codecs.


Written in the form of letters and diary entries, Dracula chronicles the vampire's journey from his Transylvanian castle to the nighttime streets of London. There, he searches for the blood he needs to stay alive - the blood of strong men and beautiful women - while his enemies plot to rid the world of his frightful power.
3 May. Bistritz -- Left Munich at 8:35 p.m. on 1st May, arriving at Vienna early next morning; should have arrived at 6:46, but train was an hour late. Buda-Pesht see,s a wonderful place, from the glimpse which I got from it from the train and the little I could walk through the streets. I feared to go very far from the station, as we had arrived late and would start as near the correct time as possible. The impression I had was that we were leaving the West and entering the East; the most Western of splendid bridges over the Danube, which is here of noble width and depth, took us among the traditions of Turkish rule.


I consider myself a lifelong learner. At a young age, my parents ignited my desire for knowledge by reading to me at bedtime and encouraging me to write my own stories. I fantasized about attending college as a child, so when I had to drop out, due to finances, my whole world ended. Little did I know, this was the best thing to ever happen to me. During my gap year, I discovered Information Technology. I pursued an Associate's degree in Computer Information Systems at Bunker Hill Community College and joined Hack Diversity's inaugral cohort. Additionally, I interned at Federal Home Loan Bank of Boston and HubSpot as a Help Desk Technician. While at HubSpot, I learned about software development and grew passionate about improving the user experience. Subsequently, I enrolled in Resilient Coders, a non-profit coding bootcamp for young people of color. Because of Resilient Coders, I am now a Software Developer at Hi Marley. On the side, I write blog posts for Skillist, sit on Hack Diversity's alumni board, develop features for Dough, strategize for G| Code House, and study Computer Science at Boston University. I'm involved in all these programs because I am dedicated to pursuing my education AND educating my own community. I aim to transform my community and grant them access to financial equity through technology.


Simple Result for Dart
Introduction
I needed a simple Result like Swift's Result.
I have seen people using dartz with the Either type. But for me, thats not really clear and readable.
Other solutions like result and super_enum did not fit my needs neither. So here is my solution.
Installation
Add the following to you pubspec.yaml
and replace [version]
with the latest version:
dependencies:
simple_result: ^[version]
import with
import 'package:simple_result/simple_result.dart';
Usage
To create a Result use
Result<ValueType, FailureType>.success(value)
or
Result<ValueType, FailureType>.failure(failure)
You can then 'iterate' over success or failure with:
result.when(
success(value) {
//doSomeThing with success value
},
failure(failure) {
//doSomeThing with failure
}
);
You can map the Result value type with map:
Result<User, Failure>.success(user);
final stringResult = result.map((user) => user.username);
// stringResult is of Type Result<String, Failure>
You can use convenience methods on a Result
final userResult = Result<User, Failure>.success(user);
userResult.isSuccess; // -> true
userResult.success; // -> user object.
Example
import 'package:simple_result/simple_result.dart';
final mySuccessResult = Result<String,Failure>.success('success value');
final myErrorResult = Result<String,Failure>.failure(MyFailure());
mySuccessResult.isSuccess // -> true
mySuccessResult.success; // -> 'success value'
final myStringResult = mySuccessResult
.when(
success:(value) => value.toString(),
failure:(_) => 'ERROR');
mySuccessResult.map((value) => 'StringResult'); // maps to Result<String, Failure>()
see more in example/main.dart
Libraries
- simple_result
- A simple result wrapper Use Result<ValueType, FailureType.success(value); or Result<ValueType, FailureType.failure(failure);


For each course's session, list the learners with the role of 'member' in that session and their start/end timestamps
| Name | Description |
|---|---|
| course_id | 22 character unique string identifying an individual course |
| on_demand_session_id | unique identifer for a course's session on the Phoenix platform |
| ualberta_user_id | Encrypted Coursera user id for ualberta data. |
| on_demand_sessions_membership_start_ts | the datetime of when the learner became a 'member' role for this course's session |
| on_demand_sessions_membership_end_ts | the datetime of when the learner ended his or her 'member' role for this course's session |
CREATE TABLE on_demand_session_memberships ( course_id varchar ,on_demand_session_id varchar ,ualberta_user_id VARCHAR(50) NOT NULL ,on_demand_sessions_membership_start_ts timestamp ,on_demand_sessions_membership_end_ts timestamp );


Nested Functions and Closures
As seen in the previous example, a function remembers the context in which it was declared.
JS Bin
Even when the outer scope is no longer active, the function still remembers it:
JS Bin
Which makes easy using a function as a filter:
JS Bin
or a transformer:
JS Bin
It's also useful in callbacks
JS Bin
Next: Install Node.js


Basics
include/xse/Connection.hpp
include/xse/linux/Connection.hpp
include/xse/HttpMethod.hpp
include/xse/HttpServer.hpp
include/xse/HttpStatus.hpp
include/xse/HttpVersion.hpp
include/xse/exceptions/NetworkException.hpp
include/xse/Request.hpp
include/xse/Response.hpp
include/xse/linux/Socket.hpp
include/xse/Socket.hpp
include/xse/TaskExecutor.hpp
include/xse/XServer.hpp


val type: ConsumedStateType
The type of consumed state: either a reference input state or a regular input state.
type - The type of consumed state: either a reference input state or a regular input state.
type


Why are input and output iterators separate categories from forward iterators if no standard
container requires the distinction? One reason is that not all iterators are associated
with containers. For example, if c
is a container that supports push_back
, then
back_inserter(c)
is an output iterator that meets no other iterator requirements.
As another example, the standard library provides iterators that can be bound to input
and output streams. Not surprisingly, the iterators for istream meet the requirements
for input iterators, and those for ostream
s meet the requirements for output iterators.
Using the appropriate stream iterator, we can use the normal iterator operations to
manipulate an istream
or an ostream
. For example, ++
will advance the iterator to the
next value in the stream. For input streams, *
will yield the value at the current position
in the input, and for output streams, *
will let us write to the corresponding ostream
.
The stream iterators are defined in the <iterator>
header.
The input stream iterator is an input-iterator type named istream_iterator
:
vector<int> v; // read ints from the standard input and append them to v copy(istream_iterator<int>(cin), istream_iterator<int>(), back_inserter(v));
As usual, the first two arguments to copy
specify a range from which to copy. The first
argument constructs a new istream_iterator
, bound to cin
, that expects to read values
of type int
. Remember that C++ input and output are typed operations: When we
read from a stream, we always say the type of value that we expect to read, although
those types are often implicit in the operations that do the read. For example,
getline(cin, s) // read data into a string cin >> s.name >> s.midterm >> s.final; // read a string and two doubles
Similarly, when we define a stream iterator, we must have a way to tell it what type it should read from or write to the stream. Accordingly, the stream iterators are templates.
The second argument to copy
creates a default (empty) istream_iterator<int>
,
which is not bound to any file. The istream_iterator
type has a default value with
the property that any istream_iterator
that has reached end-of-file or is in an error
state will appear to be equal to the default value. Therefore, we can use the default value
to indicate the "one-past-the-end" convention for copy.
We can now see that the call to copy
will read values from the standard input until we
hit end-of-file or an input that is not valid as an int
.
We cannot use an istream_iterator
for writing. If we wish to write, we need an
ostream_iterator
, which is the iterator type whose objects we use for output:
// write the elements of v each separated from the other by a space copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
Here we copy the entire vector
onto the standard output. The third argument constructs
a new iterator bound to cout
, which expects to write values of type int
.
The second argument used to construct the ostream_iterator<int>
object specifies
a value to be written after each element. Typically, this value is a string literal. If we
do not supply such a value, the ostream_iterator
will write values without any separation.
Therefore, if we omit the separator, a call to copy will run all the values together
into one unreadable mess:
// no separation between elements! copy(v.begin(), v.end(), ostream_iterator<int>(cout));


Fourth Week of RC
Day 3: The simple joy of TDD
March 12, 2019
Yesterday I submitted a pull request to OpenOversight to display officers who were employed within a certain year. Now that I've worked through earlier challenges, like how to debug a dockerized application, I'm making progress at a faster rate on OO. It's a good feeling.
The "browse department" page already included one form to browse officers by demographic information. I'd initially thought about making a separate form to view officers by year, but I decided to instead integrate the year selector into the existing form. The front-end went from looking like this:
to this
Simple, but effective.
Adding the year selector required a little bit of finagling with Python datetime objects. Each officer has a last_employment_date
field, which is null for officers who still work for a specific department. The utils.py
file has a method called filter_by_form()
, that compares officers to the information a user puts into a form. I added the below code to filter by year
Since the form selects for year and officers' last_employment_date
fields are, well, dates, I couldn't immediately compare year
and last_employment_date
. Instead, I created the min_employment_date
, which is the first day of the selected year. Next, I wrote a filter to return any officer whose last_employment_date
occurs after the min_employment_date
, or who doesn't have any min_employment_date
.
After implementing the year filter, a bunch of existing tests for the browse form broke. I got key errors because the existing tests didn't include the browse form's new field of year
. This reminded me how much I appreciate TDD and its quiet dependability. Is there a way I can TDD household chores? It's worth a google.


Babble-rnn: Generating speech from speech with LSTM networks
Phil Ayres
<EMAIL_ADDRESS>25 May 2017
There is plenty of interest in recurrent neural networks (RNNs) for the generation of data that is meaningful, and even fascinating to humans. Popular examples generate everything from credible (but fabricated) passages from Shakespeare, incredible (but highly likely) fake-news clickbait, to completely simulated handwritten sentences that shadow the style of the original writer.
These examples by prominent authors have helped many people, including myself, to see that there is more to deep-learning than just recognising cats and dogs in social media photos:
Such compelling examples demonstrate the power of recurrent neural networks for modelling human generated data, and recreating completely new data from those models. They can be understood by anybody, humanising the world of “AI”.
Inspired, Consected, sponsored research into the use of machine learning to generate new speech by modelling human speech audio, without any intermediate text or word representations. The idea is to learn to speak through imitation, much like a baby might. The goal is to generate a babbling audio output that emulates the speech patterns of the original speaker, ideally incorporating real words into the output.
A requirement of this project is to evaluate the use of commonly researched neural network architectures that can be implemented directly with the Keras toolkit. Rather than merely being a simple implementation of existing research, our project investigates the use of an alternative form of audio encoding, which we are not aware of being used previously in machine learning. We also trial various training regimes to attempt to achieve faster or improved convergence of the model.
Of course, the source code for the networks, generated models, plus Jupyter / iPython notebooks acting as a training / testing dashboard are provided for reference.
If you want to skip the details and just hear the results, jump to Additional Generated Audio
The existing research we have found related to audio generation with RNNs has provided some background to the problem and possible neural network architectures. These papers have taken various approaches to the actual representation of audio data or are focused on a more traditional text-to-speech problem. Key references are:
Note: a list of all references appears towards the end of this document
A significant hurdle in the generation of audio by a neural network is the learning of audio sequences over significant periods of time, when faced with the amount of data required to represent a coherent audio sequence.
When attempting to learn a model with a wide-spectrum waveform (CD quality) directly from the pulse coded modulation (PCM) digitised representation of the audio, the network would be required to learn a sequence of 44,100 samples of 16-bit data for a single second of audio. Add to this the desire to generate more than just a short burst of audio, and raw digitised audio data seems like an unrealistic source for learning. Surprisingly, Nayebi and Vitelli suggest that their GRUV network could generate some coherent audio despite this.
John Glover took a potentially more feasible approach when generating musical instrument sounds, by relying on a Phase Vocoder representing audio as short-time Fourier transforms. This represents the audio data in a significantly compressed form, reducing the length of sequences to be learned significantly. My observation would be that this is potentially a more meaningful audio representation. Rather than pushing continuous amplitude variations (which are highly affected by volume, noise and timing for even simple waveforms) directly into an RNN, sets of frequency and phase values are instead used. I look at this as the difference between an RNN learning and generating text using ASCII characters, rather than attempting to model raw bit streams from the text corpus.
For our research, I was hoping to rely on a more specialised encoder, specifically focused on speech data. Initially I considered the GSM “mobile phone” standard, recalling that its compression was based on vocal tract modelling. Looking at the GSM 06.10 “Full Rate Speech Transcoding” standard, I observed that the complexity of the codec was much greater than anticipated, and the data contained in each frame was less representative of a short segment of audio than expected.
This led my to search for other encoding approaches. I fortunately found Codec 2, an open source model for digitising speech audio for broadcast over HF/VHF and ham radio. The codec uses harmonic sinusoidal coding tuned to speech. In summary, it encodes a combination of primary pitch and energy of an audio frame, followed by related harmonics, plus some flags indicating voiced (vowels), or unvoiced (consonants). The codec importantly captures the fundamentals of vocal audio as individual parameters for short audio segments (see Codec 2 Data Frame for details).
A major appeal of Codec 2 is that the harmonic sinusoidal coding relates all encoded harmonic components back to the primary frequency. In this way, it is expected that similar phonetic sounds (phones being the more correct terminology) made by different speakers at different pitches are more likely to appear related in data.
Another appeal of Codec 2 for this research is that it utilises a vocal model for compression and a data representation that maintains the underlying audio components. In the 1300bps version of the codec a single 16 parameter frame represents 40ms of audio, such that just 25 frames are required per second (a rate of 'symbols' to be learned of approximately 1/1700 of 44.1kbps PCM), with more meaningful vocal representation. Based on this, it was assumed that an RNN could likely model audio sequences directly from the data, rather than requiring additional preprocessing or convolutional layers.
In order to validate the likelihood of the model making reasonable predictions from one frame to the next, a rapid analysis of Codec 2 Parameter Progression was made, to show the spread of parameters, delta from one frame to the next, and likely sensitivity to loss in the model.
The neural network architecture used in this project is quite common in the literature over the last few years. I selected it based on research of the most effective RNN architectures for sequence generation that can also be implemented using standard layers in the Keras toolset. Simply described, the network takes Codec 2 encoded audio as its input, and utilises three long short-term memory LSTM layers (ref: 10) with a final fully connected rectified linear unit (ReLU) layer. In the figure below showing the architecture, the top-most orange components are used only during generation of new speech samples.
predicted Codec 2 encoded audio
|
unscale Codec 2 frame data
|
output: 16 elements
|
[Dense 1: 16 ReLU units]
|
[LSTM 3]
|
[LSTM 2]
|
[LSTM 1]
|
input: 16 elements
|
scale Codec 2 frame data
|
Codec 2 encoded audio
Prior to training the network, the raw audio data is encoded as a Codec 2 file (MP3 to raw PCM having already been performed), where each frame of data consists of 16 parameters of one (unsigned) byte each. Scaling of the Codec 2 frames takes each parameter and scales it to a floating point number between 0 and 1, based on a constant scaling factor for each parameter. Each scaled frame of 16 values now represents a single timestep input to LSTM 1.
Since our network is attempting to return real values rather than being structured for classification tasks with a final softmax layer (ref: 11), it culminates in a single fully connected ReLU layer (named Dense 1) on the output of the three layers of LSTMs. ReLU was selected based on its relative simplicity, a good fit to the data (positive, real numbers) and successful application in previous research.
Various layer sizes, optimizers and training regimes were tested early on in the project. These included:
Three layers of LSTMs appears to be a common architecture for generator RNNs in the literature, and our preliminary testing confirmed this with medium-length LSTM layers. With a longer LSTM (320 units), two layers performed as effectively as three layers of 160 units. Short layers (40 units each) performed poorly, as did a rapid test with GRUs rather than LSTM units.
Similar to Graves, we found that it was necessary to train LSTM layers from the bottom up. Intuitively this allows the lowest level LSTM to achieve its own convergence rapidly, then allow the higher layers to build off of this. Attempting to train all three LSTMs simultaneously led to a sub-optimal model.
Our eventual regime trained LSTM 1 for 60 epochs, LSTM 1 and LSTM 2 for the next 180 epochs, and LSTM 3 only thereafter. The number of epochs selected for each breakpoint was based purely on observation of loss during initial tests. In future research we might evaluate this approach in more detail
The optimiser we found most appropriate was Nadam, the version of Adam with Nesterov momentum (ref: 12). This optimiser allowed us to focus on adjusting other architectural variables, rather than fine tuning the learning rate hyperparameter.
Losses measured during learning were handled by scaling each Codec 2 parameter back to its real scale prior to loss calculation. This allowed a one bit error in fundamental frequency (W0) for example to be considered equivalent in impact to a one bit error in a “voiced” flag, rather than 1/128 if measuring loss on the 0-1 scaled data.
Training our network only requires an extended set of clear speech, and does not need other tagging or classification. Verkaro Audiobooks was the source of our corpus: A Tale of Two Cities. A narration with a female voice was selected, in order to be clearer under heavy compression.
The chapters were concatenated to provide about 16 hours of speech from a single performer. The audio was converted to 8kbps 16-bit raw PCM data prior to Codec 2 encoding. The total length is approximately 1.5 million 16 byte Codec 2 frames after conversion.
Training used a batch length of 200 frames, equivalent to 8 seconds of speech, overlapping so that batches were picked starting every 20 frames.
The proof of the performance of the babble-rnn is less about the measured loss and more in the observed quality of audio produced. Sample generation uses feed-forward of the network, a frame at a time, based on the Keras example LSTM text generation procedure:
Since the network prediction returns a full Codec 2 frame as the 16 output values, the network effectively generates 40ms of audio per sample.
We generate 400 samples per test periodically through the training process. To evaluate performance we view both charts of Codec 2 data and PCM waveforms, and listen to generated audio. From an audible perspective we are looking for generated audio that follow the pace of the original narration, a natural pitch inflection and some proximity of tonal sequences to real words.
The overall loss the model converges to while learning is not entirely meaningful (there is no ‘correct’ answer); despite this the results are interesting. The best mean absolute error per frame we achieved is close to 0.15 at best. Intuitively this suggests that next sample predictions are close to the required fundamental frequency, harmonics and voicing, since each frame parameter is an integer.
Unfortunately, short LSTMs (40 units each) appear to be unable to converge on a meaningful result, instead producing a frame sequence of one continuous pitch, or a pulsating tone. Intuitively, the lowest LSTM layer may be unable to model sufficient variation in frame sequences, settling instead on a generalised but insufficiently complex output.
Medium sized LSTMs (160 units) converge on a mean absolute loss of about 0.55.
The audio generated does manage to achieve some structure that has sequences of roughly word length. The pitch was variable and vaguely natural. Overfitting does not appear to be an issue.
Note: in the following examples, the initial 64000 samples (8 seconds of audio or 200 Codec 2 frames) are the seed sequence, included for reference. A plot of the generated audio waveform, and scaled plot of Codec 2 frame parameters are also shown to highlight the data being generated.
Since training is performed from lowest to highest layers, it is instructive to also hear the results earlier in the training.
First layer trained:
Second layer trained:
Long LSTMs (320 units) converge on a mean absolute loss around 0.15.
The audio generated manages word and sentence length sequences. Occasional babblings vary in pitch wildly, and overall the result seems maybe less comprehensible than the shorter 160 unit LSTM layer results.
Additional Generated Audio provides longer sequences that allow additional comparison of the results. In these, the 320 unit LSTMs perform a little better to my ear.
A neural network architecture of deep LSTM layers has been shown to be able to learn to model speech-based audio when encoded by a codec designed specifically for vocal compression. When run as a feed-forward generator, the network manages to predict samples that track the original pace of the vocal performer with natural sounding pitch inflections over the course of sentences, and short term sequences that might reflect the structure of words.
Although we were unable to achieve a model that actually generated true words, the model could be considered to babble in a reasonably acceptable way.
Listening to the corpus with the current Codec 2 compression rate (1300bps) reveals that the compression obscures many vocal phones significantly. In future we would attempt training a similar network with a higher bit-rate version of Codec 2. Although the codec data will be higher resolution and therefore may require more effort to learn, it is also possible that the resolution of data could result in less confusion between similar sounds and therefore make it easier for the model to flow to a more coherent next sound during prediction. From a qualitative standpoint, it is likely to also be better for human evaluation to understand the results.
Intuitively, it seems that the ability to evaluate loss not just on the next frame, but several frames ahead could help guide learning more rapidly. Although this may be considered double dipping from a formal standpoint, and increase complexity for learning with back propogation through time, understanding both the theoretical and practical implications of this deserve additional research.
Application of alternative neural network architectures should be considered in the future. Mixture Density Networks as used in Graves handwriting generation (ref: 2) and Glover musical instrument sound generation (ref: 4), based on the paper by Bishop (ref:15), seems to be worth evaluation. Similarly, HyperLSTM (ref: 14) and Diagonal LSTM (ref: 9) have shown some success in sequence generation and we would consider future research with these structures.
For reference, each frame of Codec 2 data is represented as:
|
number of bits per parameter |
parameter type |
|
1,1,1,1 |
voiced flags for 4 (10ms) PCM ‘frames’ |
|
7 |
W0 (fundamental frequency, “pitch”) |
|
5 |
E (energy of W0) |
|
4,4,4,4,4,4,4,3,3,2 |
LSP (spectral magnitudes) |
A rapid graphical evaluation of the progression from one Codec 2 frame to the next was performed. The objective was to see the likely impact on audio output based on small errors in each value.
The charts each show a single Codec 2 parameter over a significant sample of the corpus. The charts show current value on the horizontal axis, against next value on vertical axis. This rapid visualisation shows how the progression from one frame to the next is generally clustered, although varies between parameters.
Fundamental frequency
The strong cluster around x=y follows an expectation that the primary vocal pitch does not rapidly fluctuate over time. Interestingly, at the lower end there is more variation.
Energy
Variations in energy vary more at the top end of the range. It is not expected that errors here will lead to incomprehensible speech.
Spectral magnitudes
These vary significantly from one frame to the next. The parameters with the greatest range again follow x = y to a limited degree. Lower range parameters appear to have a greater spread of next values.
“Voiced” parameters
These do not show any consistent progression over time. Only one of the parameter charts is shown as they appear identical.
60 seconds of audio generated by a 3 x 320 unit LSTM. The initial 8 seconds are seed data from the original corpus.
60 seconds of audio generated by a 3 x 160 unit LSTM. The initial 8 seconds are seed data from the original corpus.


And so we reach the half-way mark in our serial story, The Immense Duckpond Pamphlet, which began with A Is For Aminadab on 23 January
Moop's shoes had been shoved into a red plastic basin on the floor underneath her sink. Each tuberologist had their own sink. Doctor Cack had insisted upon it.
Moop did not use her sink very often. She was more of a field worker, scurrying about the grounds armed with a plethora of ludicrous scientific equipment, trailing wires and mulverts behind her as she darted from potato patch to potato patch, scribbling drivel into her notebook with an exciting new propelling pencil.
She was a sly one, was Moop. She wrote in Glagolitic script to safeguard her memoranda from prying eyes. Several of her projects had only a tangential relationship to Doctor Cack's researches. Had he but known!


Experimental. Requires the rfishbase package to be installed.
sol_fb_length_weight( ..., input_properties, return_properties, worms = requireNamespace("worrms", quietly = TRUE) )
| ... | : arguments passed to rfishbase::length_weight |
|---|---|
| input_properties | character: an optional vector of properties (see |
| return_properties | character: an optional vector of properties (see |
| worms | logical: if TRUE, and if the worrms package is installed, try and find the AphiaID for the taxon in the World Register of Marine Species |
A tibble of equation(s)
if (FALSE) { library(dplyr) eq <- sol_fb_length_weight("Electrona antarctica", input_properties = "standard length") x <- tibble(SL = 10) %>% mutate(SL = sol_set_property(SL, "standard length", with_units = "cm")) sol_allometry(x, eq) }


The role of QA is changing. At Experience 2021, learn how to navigate the cultural shift to quality engineering across two days of sessions, workshops, and discussions.
Register for your free pass
.
x


Anger, Rage, Confusion, Depression...

  I don't understand life. I don't understand the world. I'm hurt, I'm angry, upset, and most definitely, confused. I don't know how to cry anymore... I don't know how to talk. .. I feel so alone in this over-populated, crazy world we live in.
God, I know you're out there.. I know I haven't come to you (and probably should).  I strive for perfection, and it's yet to come. I have ABSOLUTELY no interest in continuing anything I've begun...
Is life one of your fun little experiments to test the human form? To ensure that our neurologically-advanced brains are capable of destructing what you've created?  But wait.. it cant just be something that we make a mistake with once - it has to be a never-ending mistake... something we cant run away from... something we feel chained to - something that is physically capable of changing our own brain chemistry - tricking us into thinking it's the only thing that will make us happy. Is it really a trick? Or is it actually possible that I may never be able to express feelings again - because I've polluted passages in my brain that make it possible for me to laugh, and cry, and feel pain .... ?
How can someone be so fuckin' blind? I offered information. I'm CRYING out for help! Can't he hear me???? I've become numb to his love. I don't know how to accept it anymore. My personality is a monotone... a grey-ish blur. I've put up a wall to detour criticism, and ensure I'm not vulnerable to the comments I'm just waiting to hear. .. but my wall has grown so tall, I don't think I'm capable of climbing over it anymore. You don't deserve me.  You deserve someone with stability and self-control - not me.
I look in the mirror and see someone I never expected I'd see again. She's hurt.. She's in pain.. but why is she the only one capable of comprehending that???  It's swallowed my soul - but worse - I'm still alive. I'm here watching my own body deteriorate to nothing... My insecurities are eating me alive!... There's a thin line between sane and crazy ... a thin white line of microscopic crystals.
I still have self-realization. I still know what I'm doing - maybe it's not too late to end this self-destruction ... ?
I've been up for days, and have accomplished NOTHING.
I wish I had never given in again. It had been two years....  And then, one day, the DEVIL came knocking at my door, and of course I had to let him in. It's not self-experimentation anymore - it's self-mutilation. My face is covered in (what looks like) a bad breakout. My teeth hurt, my nose is scabbed inside, and my bones ache like those of an 80 year old. - The only good news is that I fit into a size 0 again...
 I'm angry, saddened, unhealthy, pissed... and SO DAMN CONFUSED!...  - I'll continue this another day...
good night.
Experience Reports are the writings and opinions of the individual authors who submit them.
Some of the activities described are dangerous and/or illegal and none are recommended by Erowid.


Customers use social media sites more and more to ask for help, write reviews, and document all their interactions with businesses.
Are you able to keep up?
Here at API we'll help you gather all comments and concerns from all social media sites. That way you can spend more time interacting with customers and less time navigating between platforms.
Simply browse our catalog, choose the sites that fit your clientele, and let us do the rest!
We're Here To Save You Time!!!
WOW! I never realized how much time I spent looking up customer reviews about my ice business. With API I've cut my response time in half! That means I spend more time mining ice and less time mining data!!!

Huzzah! Businesses never had it so easy! Why I remember back in my day, I had to read every letter and respond with a stamped envelope. I can't believe how easy API has made compiling my online customer feedback. Well worth the money!!
Reach out to us with your social media needs at info@apiservices.com